V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jorneyr
V2EX  ›  程序员

Go 的范型怎么把 Response[指定类型] 转换为 Response[any]

  •  
  •   jorneyr · 2023-01-28 10:52:36 +08:00 · 3027 次点击
    这是一个创建于 658 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题: Go 的范型怎么把 Response[指定类型] 转换为 Response[any].

    下面的示例代码在 [3] 部分进行类型转换的时候报错 cannot use rsp2 (variable of type Response[map[string]string]) as Response[any] value in assignment 。

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Response[T any] struct {
    	Code    int    `json:"code"`           // 业务逻辑相关的 code ,不是 HTTP Status Code
    	Success bool   `json:"success"`        // 业务逻辑处理成功时为 true ,错误时为 false
    	Msg     string `json:"msg"`            // 请求的描述
    	Data    T      `json:"data,omitempty"` // 请求的 payload
    }
    
    func main() {
    
    }
    
    func foo[T any]() Response[any] {
    	// [1] 创建范型对象
    	rsp1 := Response[map[string]string]{
    		Code:    200,
    		Success: true,
    		Data:    map[string]string{"1": "one", "2": "two"},
    	}
    
    	// [2] 范型序列化
    	b, _ := json.Marshal(rsp1)
    	fmt.Println(string(b))
    
    	// [3] 范型反序列化
    	var rsp2 Response[map[string]string]
    	json.Unmarshal(b, &rsp2)
    	fmt.Printf("%+v\n", rsp2)
    
    	// [4] 范型向上类型转换
    	// 相当于 Java 的范型用 ? 来接收任意类型的范型如
    	//     List<String> list1 = new LinkedList<>();
    	//     List<?> list2 = list1;
    	rsp3 := Response[any]{}
    	rsp3 = rsp2 // 错误: cannot use rsp2 (variable of type Response[map[string]string]) as Response[any] value in assignment
    
    	return rsp3
    }
    
    29 条回复    2023-01-29 15:34:21 +08:00
    nobot
        1
    nobot  
       2023-01-28 11:01:06 +08:00
    返回值需要明确知道类型,不然编译器,无法特例化对应类型的函数
    jorneyr
        2
    jorneyr  
    OP
       2023-01-28 11:05:21 +08:00
    @nobot 还不到返回值类型的地方,rsp3 = rsp2 赋值的时候就报错了。
    nobot
        3
    nobot  
       2023-01-28 11:06:23 +08:00
    返回 Response[T]
    @jorneyr
    hahadaxigua834
        4
    hahadaxigua834  
       2023-01-28 11:12:40 +08:00
    func (x *Response[T]) ToAny() Response[any] {
    return Response[any]{
    Code: x.Code,
    Success: x.Success,
    Msg: x.Msg,
    Data: x.Data,
    }
    }
    jorneyr
        5
    jorneyr  
    OP
       2023-01-28 11:12:58 +08:00
    @nobot 我想定义一个函数,接收任意类型的 Response[any],然后处理后返回给客户端,所以这个函数的目录就是接收任意类型的 Response ,也就是上面用 rsp3 = rsp2 进行演示。

    返回 Response[T] 满足不了需求。
    rrfeng
        6
    rrfeng  
       2023-01-28 11:17:11 +08:00
    你都上泛型了为啥要 any……是不是跑偏了
    kaf
        7
    kaf  
       2023-01-28 11:17:14 +08:00
    编译错误说明了 Response[map[string]string]和 Response[any]是两个不同类型的参数
    jorneyr
        8
    jorneyr  
    OP
       2023-01-28 11:17:29 +08:00
    @hahadaxigua834
    谢谢,返回前调用这个方法转换一下能满足要求。
    hahadaxigua834
        9
    hahadaxigua834  
       2023-01-28 11:18:07 +08:00
    @hahadaxigua834 最快的方法就是做个 copy
    jorneyr
        10
    jorneyr  
    OP
       2023-01-28 11:18:29 +08:00
    @kaf Java 的范型 ? 可以接收任意类型的,就想看看 Go 里能不能也用相似的办法实现。
    kaf
        11
    kaf  
       2023-01-28 11:21:57 +08:00
    @jorneyr
    func foo[T any](in T) Response[T] {
    // [1] 创建范型对象
    rsp1 := Response[T]{
    Code: 200,
    Success: true,
    Data: in,
    }
    return rsp1
    }
    你是说这样的功能吗,go 的泛型使用就是你确定入参和返回类型的情况下
    jorneyr
        12
    jorneyr  
    OP
       2023-01-28 11:28:46 +08:00
    @kaf
    // 像下面这样,接收 Response[string], Response[int] 等任意类型的范型参数进行统一处理。
    // 业务代码里可能生成 Response[string], Response[AgentStats] 等不同类型的响应对象,这些对象都会在下面的 responseCommonHandle 函数中统一处理例如某些情况下打印日志。
    func responseCommonHandle(rsp Rsponse[any]) {

    }
    OuJin
        13
    OuJin  
       2023-01-28 11:28:48 +08:00
    @jorneyr

    func main() {

    // 返回数据
    var (
    response1 = Response[map[string]string]{} // 返回 1, 数据类型 map[string]string
    response2 = Response[map[int]int]{} // 返回 2, 数据类型 map[int]int
    // ...
    )

    // 模拟接收到的数据
    var (
    body1, _ = json.Marshal(response1)
    body2, _ = json.Marshal(response2)
    )

    // 解析
    var (
    result1, err1 = foo[map[string]string](body1) // 指定 T 类型为 map[string]string
    result2, err2 = foo[map[int]int](body2) // 指定 T 类型为 map[int]int
    )

    fmt.Println(result1, err1)
    fmt.Println(result2, err2)
    }

    func foo[T any](body []byte) (response Response[T], err error) {
    err = json.Unmarshal(body, &response)
    return
    }

    在调用 foo 时指定类型,看看这样满不满足要求
    jorneyr
        14
    jorneyr  
    OP
       2023-01-28 11:36:53 +08:00
    @OuJin 谢谢,我的问题重点不是在序列化和反序列化方面 (提问的时候应该去掉,加上只是为了验证序列化功能在范型的时候可以正常使用)。

    我的问题主要是在不知道 Go 里有没有一个像 Java 范型那样: 定一个范型类型,可以接收任意类型的范型对象,也就是下面这个例子:
    List<String> list1 = new LinkedList<>();
    List<?> list2 = list1;
    bigboNed3
        15
    bigboNed3  
       2023-01-28 11:40:21 +08:00
    @jorneyr
    ```
    // [4] 范型向上类型转换
    // 相当于 Java 的范型用 ? 来接收任意类型的范型如
    // List<String> list1 = new LinkedList<>();
    // List<?> list2 = list1;
    rsp3 := Response[any]{Code: rsp2.Code, Success: rsp2.Success, Data: rsp2.Data}

    ```
    kaf
        16
    kaf  
       2023-01-28 11:40:22 +08:00
    @jorneyr 下面这段代码是否是你想要的功能
    ```
    package main

    // 定义一个结构体,Data 是一个泛型接口
    type Response[T ResponseHandle] struct {
    Code int `json:"code"` // 业务逻辑相关的 code ,不是 HTTP Status Code
    Success bool `json:"success"` // 业务逻辑处理成功时为 true ,错误时为 false
    Msg string `json:"msg"` // 请求的描述
    Data T `json:"data,omitempty"` // 请求的 payload
    }

    type ResponseHandle interface {
    log()
    }

    type AgentStats struct {
    Status int
    }

    func (r *AgentStats) log() {
    // do something
    }

    func main() {

    }

    // 输入泛型的 resp,在函数中执行相关的方法
    func responseCommonHandle[T ResponseHandle](rsp Response[T]) {
    rsp.Data.log()
    }
    ```
    jorneyr
        17
    jorneyr  
    OP
       2023-01-28 11:44:35 +08:00
    @kaf 不是的,如果用接口的方式,每个类型都要实现接口,反而工作量更大了,只是想要一个纯粹的存放数据的结构体。
    lysS
        18
    lysS  
       2023-01-28 11:47:47 +08:00
    T[any] 这种泛型没啥意义,还有进一步的性能损失,不如直接 any 断言
    kaf
        19
    kaf  
       2023-01-28 11:51:38 +08:00
    @jorneyr 那其实输出类型 T ,返回类型 T 即可,你应该使用 T 类型而不是使用 any ,any 只是封装的 interface 类型,go 的泛型并不是 Java 的泛型,Java 的所有对象继承于 Object ,在 go 中每个结构体都是单独的类型,并不能强转,而且你需要在函数定义是知道输入什么类型,类似于 interface 可以接受任意类型参数,而定义泛型之后,编译器知道了 interface 是你定义的泛型结构体中的一个
    kaf
        20
    kaf  
       2023-01-28 11:54:32 +08:00
    @jorneyr 可以看 go 实现 Java 的 stream 的 map 方法
    // Map manipulates a slice and transforms it to a slice of another type.
    // Play: https://go.dev/play/p/OkPcYAhBo0D
    func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R {
    result := make([]R, len(collection))

    for i, item := range collection {
    result[i] = iteratee(item, i)
    }

    return result
    }
    we8105
        21
    we8105  
       2023-01-28 14:13:43 +08:00
    我也有类似的问题
    jorneyr
        22
    jorneyr  
    OP
       2023-01-28 14:27:36 +08:00
    @kaf 与你的思路还是不太一样的。
    exonuclease
        23
    exonuclease  
       2023-01-28 14:51:39 +08:00
    Response<T>不能直接用吗 c#里面是可以的啊 比如这个函数
    private static TValue getValue<Tkey,TValue>(KeyValuePair<Tkey,TValue> kvp)
    {
    return kvp.Value;
    }
    jorneyr
        24
    jorneyr  
    OP
       2023-01-28 15:19:26 +08:00
    @exonuclease 在 go 里不可以呢。
    BeautifulSoap
        25
    BeautifulSoap  
       2023-01-28 16:19:24 +08:00   ❤️ 1
    Go 的泛型类型都必须经过实例化。泛型实例化之后的类型就成为了和 int, string, float32 这些类型一样确定的类型

    你用 map[string]string 实例化泛型类型 Response[T] 的话,那么实例化之后的类型就是 Response[map[string]string]。用 any 实例化的话实例化得到的则是 Response[any]。编译器的类型检查是不会去关心你这泛型类型到底是怎么个结构的,所以你把一个 Response[map[string]string] 类型的变量赋值给另一个 Response[any] 类型的变量那肯定要报错(在编译器的眼里,这相当于你把 int 类型变量赋值给 string 变量一样)

    有兴趣可以看看我之前写的关于泛型的教程,go 目前的泛型并不是完整功能的泛型,所以并不能做到其他教程里那样理所当然的事情
    https://www.v2ex.com/t/844084
    BeautifulSoap
        26
    BeautifulSoap  
       2023-01-28 16:20:35 +08:00
    @BeautifulSoap 打错字“go 目前的泛型并不是完整功能的泛型,所以并不能做到其他语言里那样理所当然的事情”
    jorneyr
        27
    jorneyr  
    OP
       2023-01-28 16:37:16 +08:00
    @BeautifulSoap
    嗯,那我这个想统一处理的方式,还想接口好看,在 go 里估计就行不通了。
    lazyfighter
        28
    lazyfighter  
       2023-01-29 14:50:53 +08:00
    我在想 java 是因为任意的对象都继承自 object ,所以泛型 T 不指定就变成了 Object 引用,即父类指向子类引用, 所以感觉 go 里面应该用 interface ,纯属猜测
    jorneyr
        29
    jorneyr  
    OP
       2023-01-29 15:34:21 +08:00
    @lazyfighter 用 interface 的话,我这个场景的统一返回对象直接不使用范型就可以了。

    type Response struct {
    Code int `json:"code"` // 业务逻辑相关的 code ,不是 HTTP Status Code
    Success bool `json:"success"` // 业务逻辑处理成功时为 true ,错误时为 false
    Msg string `json:"msg"` // 请求的描述
    Data any `json:"data,omitempty"` // 请求的 payload
    }
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2863 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 07:49 · PVG 15:49 · LAX 23:49 · JFK 02:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.