V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
zmqiang
V2EX  ›  Go 编程语言

无法通过反射将 interface{}转换成结构题的问题

  •  
  •   zmqiang · 2022-06-15 16:17:53 +08:00 · 2085 次点击
    这是一个创建于 915 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近写了个小 demo ,实现通过反射将结构体数组,转换成结构实现的接口类型的数组,到这部分没有问题。但是当打算从接口数组转换回结构结构体数组的时候,却转换失败,不知道原因是什么

    代码见 https://go.dev/play/p/ajYUDEs6ssQ

    如果不是通过反射,是可以通过类型断言将接口类型转换成底层的结构体,因为 go 的接口类型保存有底层结构的类型信息,但是通过反射却无法转换。

    本来打算看看 go 反射的底层实现找找原因,但是水平有限没找到……有大手子可以帮忙解解惑吗?

    11 条回复    2022-06-16 11:21:14 +08:00
    Buges
        1
    Buges  
       2022-06-15 17:06:07 +08:00 via Android
    因为转换成 Stringer 的时候 box 了一次,底层类型变成了 interface 类型。
    加个检查,对 interface 解包
    if fe.Kind() == reflect.Interface {
    fe = fe.Elem()
    }
    setsunakute
        2
    setsunakute  
       2022-06-15 17:06:14 +08:00
    reflect.Value.Convert 支持 interface -> interface, 具体类型 -> interface 的转换, 不支持 interface 的转换

    可以将 te.Set(fe.Convert(te.Type())) 修改为 te.Set(reflect.ValueOf(fe.Interface())), 这样就可以过了
    setsunakute
        3
    setsunakute  
       2022-06-15 17:09:19 +08:00
    不支持 interface -> 具体类型的转换, 补充一下
    zmqiang
        4
    zmqiang  
    OP
       2022-06-15 17:44:42 +08:00
    @Buges 可以理解为,当*Data 传给 reflect.ValueOf 时,做了*Data->interface{}的转换,然后 convert 函数里,做的是 interface{} -> fmt.Stringer 的转换。这样 fmt.Stringer 的底层类型就变成了 interfaces{},*Data 的信息是包在了 interface{}里,fmt.Stringer 中是没法直接获取到?
    zmqiang
        5
    zmqiang  
    OP
       2022-06-15 18:12:42 +08:00
    @setsunakute 哪里可以了解下为什么会有不支持 interface -> 具体类型转换?是 go 的类型系统设计的原因吗?
    Mitt
        6
    Mitt  
       2022-06-15 21:27:02 +08:00
    fmt.Printf("%+v", ss) 可以看出,你两个值是 Nil
    Mitt
        7
    Mitt  
       2022-06-15 21:35:46 +08:00
    @Mitt #6 哦不是,我的问题
    Mitt
        8
    Mitt  
       2022-06-15 22:11:29 +08:00
    @zmqiang #5

    reflect/values.go

    if implements(dst, src) {
    if src.Kind() == Interface {
    return cvtI2I
    }
    return cvtT2I
    }

    reflect/types.go

    // implements reports whether the type V implements the interface type T.
    func implements(T, V *rtype) bool {
    if T.Kind() != Interface {
    return false
    }
    ...
    }

    我猜测是这样的,Convert 的时候两个 Type 进行转换,前者是 fmt.Stringer 后者是 Data, Data => fmt.Stringer 可以因为实现了接口,但 fmt.Stringer => Data 不行, 如果两个被 Interface 抹掉了类型就会取 ValueType, 那两个底层 ValueType 都是 *Data 所以可以
    SingeeKing
        9
    SingeeKing  
       2022-06-15 22:27:30 +08:00 via iPhone
    原理上面已经说的差不多了,提醒下范型已经发布可用了,op 这个 demo 正好就是范型的用处
    Buges
        10
    Buges  
       2022-06-15 23:06:33 +08:00 via Android
    @zmqiang interface 是一个 fat pointer ,一个指向值,一个指向类型。正常情况下你把某个类型赋值给 interface (隐式 box )、或把一个 interface 转换成另一个 interface 是不会改变它的具体值和类型的。而 reflect.ValueOf 则是把一个 interface 里的值拿出来,并提供一些 API 。
    但是你 Convert 的时候把一个具体类型转换成 interface ,这里是对其进行了一次原地打包的。
    转换前:
    ( Data 值,Data 类型)
    转换后:
    ( Stringer 值,Stringer 类型)
    其中 Stringer 值为 ( Data 值,Data 类型)
    zmqiang
        11
    zmqiang  
    OP
       2022-06-16 11:21:14 +08:00
    @SingeeKing 确实这个老问题用泛型解决正好,已经准备将项目最低的版本设置为 1.18 了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   839 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 109ms · UTC 21:00 · PVG 05:00 · LAX 13:00 · JFK 16:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.