我的代码调用的库函数大致如下:

func Search() ([]myLibrary.SomeObject, error) {
    var results []apiv17.SomeObject
    // ...
    if (resultsFound) {
      results = append(results, someResult)
    }
    return results
}

...我的代码调用它,然后将其封送到JSON.

results, err := myLibrary.Search()
bytes, err := json.Marshal(results)

现在的问题是,由于Search函数的编写方式(假设我们无法更改它),如果没有结果,它将返回未初始化的nil切片.不幸的是,无法将encoding/json配置为将nil切片编码为[](例如,请参阅正在进行讨论的这proposal).

明确判断nil可以解决问题:

results, err := myLibrary.Search()
if results == nil {
  results = []apiv17.SomeObject{}
}
bytes, err := json.Marshal(results)

...但它还添加了对返回类型apiv17.SomeObject的显式依赖.这很不方便,因为这种类型在库中经常变化.E、 g.在下一个库版本中,它可能是apiv18.SomeObject.

有了上面的nil判断,每次发生这种情况时,我都必须更新代码.

有没有办法避免这种情况,在不显式引用其类型的情况下为变量分配一个空的非零切片?类似这样:

results = [](type of results){}

推荐答案

转到1.18

您可以使用一个泛型函数来捕获切片的基类型并返回长度为零的切片:

func echo[T any](v []T) []T {
    return make([]T, 0)
}

func main() {
    n := foo.GetFooBar()
    if n == nil {
        n = echo(n) // no need to refer to apiv17 here
    }
    bytes, _ := json.Marshal(n)
    fmt.Println(string(bytes)) // prints []
}

echo中需要正则参数v []T的目的是允许类型推断将切片[]apiv17.SomeObject与参数[]T统一起来,并推断T作为基类型apiv17.SomeObject,这样您就可以将其称为echo(n),而不需要显式类型参数.

apiv17在编译时当然是已知的,因为它是通过myPackage传递导入的,所以您可以利用这个和类型推断来避免为apiv17添加显式import语句.

这是多文件操场上的情况:https://go.dev/play/p/4ycTkaGLFpo

该类型在bar包中声明,但main只导入play.ground/foo,只使用foo.GetFooBar.


达到1.17及以下

反射只需将echo函数从上面改为取interface{}参数(Go 1.17中没有any,记得吗?)用reflect.MakeSlice做契约:

func set(v interface{}) {
    rv := reflect.ValueOf(v)
    if rv.Kind() != reflect.Ptr {
        panic("not a ptr")
    }
    reflect.Indirect(rv).Set(reflect.MakeSlice(rv.Type().Elem(), 0, 0))
}

然后将指针传递到切片,以便可以使用反射设置其值.

func main() {
    n := foo.GetFooBar()
    if n == nil {
        set(&n)
    }
    fmt.Printf("type: %T, val: %v, is nil: %t\n", n, n, n == nil)
    // type: []bar.FooBar, val: [], is nil: false
    
    bytes, _ := json.Marshal(n)
    fmt.Println(string(bytes)) // prints [] again
}

围棋1.17操场:https://go.dev/play/p/4jMkr22LMF7?v=goprev

Go相关问答推荐

在字符串与字符串子切片上使用len进行位转移产生意外输出

Zitadel示例Go Webapp加密密钥

在Uber FX中实现后台进程正常关闭的正确方式是什么?

Golang使用Run()执行的命令没有返回

如何确定泛型类型在运行时是否可比较?

如何从 nil 指针创建值

不接受来自 stdin 的重复输入

Go 中带有回调的 MiniDumpWriteDump

如何根据中间件的请求设置上下文值?获取 go-staticcheck 问题

在多个 struct 体中重用 Go 中的函数

Kperf 构建失败

如何根据地址和大小打印字符串

在 Windows 11 上运行 go mod tidy 时的 gitlab 权限问题

如何使用 go-git 将特定分支推送到远程

是否可以从 golang 中的参数推断类型?

如何在 golang 中同时加载 .env 文件和 os 环境变量

出于某种原因,Golang (Go) AES CBC 密文被填充了 16 个 0x00 字节

如何扩充 ResponseWriter 的 Header() 返回的 map

我该如何做错误处理惯用的方式

Go 1.18 泛型如何使用接口定义新的类型参数