在不引用其类型的情况下,如何给空切片赋值?

huangapple go评论82阅读模式
英文:

Assign empty slice without referring to its type?

问题

我的代码调用了一个类似这样的库函数:

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/jsonnil切片编码为[](参见例如这个提案和正在进行的讨论)。

显式检查nil可以解决问题:

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

...但它还会显式地依赖于返回类型apiv17.SomeObject。这很不方便,因为该类型在库中经常发生变化。例如,在下一个库版本中,它可能是apiv18.SomeObject

使用上面的nil检查,每次发生更改时,我都必须更新我的代码。

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

results = [](results的类型){}
英文:

My code calls a library function which looks roughly like this:

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

...and my code calls it and then marshals it to JSON.

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

Now the problem is that because of the way the Search function is written (and let's assume we can't change it), it'll return an uninitialized nil slice if there are no results. And unfortunately, there is no way to configure encoding/json to encode nil slices as [] (see e.g. this proposal with ongoing discussion).

Explicitly checking for nil solves the problem:

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

...but it also adds an explicit dependency on the return type, apiv17.SomeObject. That's inconvenient because that type frequently changes in the library. E.g. in the next library version it might be apiv18.SomeObject.

With the nil check above, I'll have to update my code every time that happens.

Is there any way to avoid this and assign an empty, non-nil slice to the variable without explicitly referring to its type? Something like this:

results = [](type of results){}

答案1

得分: 5

Go 1.18

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

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

func main() {
	n := foo.GetFooBar()
	if n == nil {
		n = echo(n) // 这里不需要引用 apiv17
	}
	bytes, _ := json.Marshal(n)
	fmt.Println(string(bytes)) // 输出 []
}

echo 中要求一个普通参数 v []T 的目的是允许类型推断将切片 []apiv17.SomeObject 统一为参数 []T 并推断 T 为基本类型 apiv17.SomeObject,这样您就可以直接调用 echo(n) 而不需要显式指定类型参数。

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

在多文件 playground 中的效果如下:https://go.dev/play/p/4ycTkaGLFpo

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


Go 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)) // 再次输出 []
}

Go 1.17 playground: https://go.dev/play/p/4jMkr22LMF7?v=goprev

英文:

Go 1.18

You can use a generic function that captures the slice's base type and returns a slice of length zero:

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 []
}

The purpose of requiring a regular argument v []T in echo is to allow type inference to unify the slice []apiv17.SomeObject with the argument []T and infer T as the base type apiv17.SomeObject, so that you can call it just as echo(n) and no explicit type parameter.

The package apiv17 is of course known at compile time because it's transitively imported via myPackage, so you can take advantage of this and type inference to avoid adding an explicit import statement for apiv17.

This is how it looks like on the multi-file playground: https://go.dev/play/p/4ycTkaGLFpo

The type is declared in bar package, but main only imports play.ground/foo and only uses foo.GetFooBar.

<hr>

Go 1.17 and below

Reflection. Just change the echo function from above to taking an interface{} argument (there's no any in Go 1.17, remember?) and do the deed with reflect.MakeSlice:

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

Then pass a pointer to the slice, so that you can set its value with reflection.

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

Go 1.17 playground: https://go.dev/play/p/4jMkr22LMF7?v=goprev

答案2

得分: 4

另一个答案描述了如何创建一个空切片。

但是,你可以更简单地解决你的原始问题:如果resultsnil,无论它的元素类型是什么,JSON编组结果都将是[]。所以如果resultsnil,不需要调用json.Marshal(),只需输出[]

results, err := myLibrary.Search()
var bytes []byte
if results == nil {
    bytes = []byte{'[', ']'} // JSON编组结果是"[]"
} else {
    bytes, err = json.Marshal(results)
    // 处理错误
}
英文:

The other answer describes how to create an empty slice.

But you can solve your original issue much simpler: if results is nil, you don't need to create a empty slice, regardless of whatever element type it would have, the JSON marshaling would be [] anyway. So if results is nil, no need to call json.Marshal(), just "output" []:

results, err := myLibrary.Search()
var bytes []byte
if results == nil {
    bytes = []byte{&#39;[&#39;, &#39;]&#39; } // JSON marshaling result is &quot;[]&quot;
} else {
    bytes, err = json.Marshal(results)
    // Handle error
}

huangapple
  • 本文由 发表于 2022年7月9日 01:10:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/72914784.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定