从接口约束的通用函数返回nil?

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

Return nil from generic func constrained to interface?

问题

我即将将现有的代码迁移到使用Go泛型。这个非常基本的示例展示了问题:

// 只是一个接口
type Object interface {
	Test() error
}

// 返回一个接口的函数可以返回`nil`
func Get() Object {
	return nil 
}

// 为什么我不能从一个泛型函数返回`nil`,而该函数受限于`interface Object`?
func GetG[T Object]() T {
	return nil // ./prog.go:15:9: cannot use nil as T value in return statement
}

如果返回interface Object的函数func Get允许返回nil,为什么受限于Object的泛型版本func GetG不允许这样做呢?

有人可以告诉我更多关于这个行为或背后的原理吗?

英文:

I'm about to migrate existing code to use go generics. This very basic example shows the problem:

// Just an interface
type Object interface {
	Test() error
}

// A func returning an interface can return `nil`
func Get() Object {
	return nil 
}

// Why I can't return `nil` from a generic func constrained to `interface Object`?
func GetG[T Object]() T {
	return nil // ./prog.go:15:9: cannot use nil as T value in return statement
}

https://go.dev/play/p/3WTg1aYDzCA

If func Get, which returns interface Object is allowed to return nil, why is the generic version func GetG where T is constrained to Object not allowed to do so?

Can anyone tell me more about the behaviour or the rational behind it?

答案1

得分: 4

类型参数不是其约束条件。

  1. 约束条件只限制可以用于实例化该类型参数的具体类型,并可能确定该类型参数支持的操作。
  2. 类型参数仅在编译时存在。在运行时,代码将使用提供给函数的具体类型参数进行操作。
  3. nil 不能赋值给具体类型参数。

直观地说,假设我使用定义好的整数类型来实现你的接口:

type Foo int8
func (Foo) Test() error { return nil }

Foo 是否满足约束条件 Object?是的,它实现了该方法。我能用 Foo 实例化 GetG 吗?是的,它满足约束条件。我能将 nil 赋值给 Foo 吗?不行,我不能

当为变量分配存储空间时,[...] 该变量或值被赋予默认值。[...] 对于指针、函数、接口、切片、通道和映射,是 nil

因此,如果返回类型是类型参数,你不能总是返回 nil。在其他情况下,你可以返回 nil

  • 如果返回类型是指向类型参数的指针,但不是 T,而是 *T
func GetG[T Object]() *T {
    return nil // 可行
}
  • 如果你可以将 nil 赋值给类型参数的类型集中的所有类型,该类型集由其约束条件确定:
// 类型集仅包括 *int 和 *string
func GetG[T *int | *string]() T {
    return nil // 可行
}

上述情况在语言规范的可赋值性中有提到:

类型 V 的值 x 可以赋值给类型 T 的变量,如果

[...]

  • x 是预声明的标识符 nil,T 是类型参数,并且 x 可以赋值给 T 的每个类型。

对于仅具有基本接口(仅方法)作为约束条件的情况,由于上述原因,此条件不成立:你可以使用 nil 无法赋值给的类型来满足它。

否则,你可以返回 T 的零值,有几种不同的方法可以实现,其中最直接的方法是:

func GetG[T Object]() T {
    var zero T
    return zero
}
英文:

A type parameter is not its constraint.

  1. the constraint only restricts the concrete types that can be used to instantiate that type parameter and may determine what operations that type parameter supports
  2. the type parameter exists only at compile time. At run time the code operates with whatever concrete type argument was supplied to the function.
  3. nil may not be assignable to the concrete type argument

Intuitively, let's say I implement your interface with a defined integer type:

type Foo int8
func (Foo) Test() error { return nil }

Does Foo satisfy the constraint Object? Yes, it implements the method. Can I instantiate GetG with Foo? Yes, it satisfies the constraint. Can I assign nil to Foo? No, I can't:

> When storage is allocated for a variable, [...] the variable or value is given a default value. [...] nil for pointers, functions, interfaces, slices, channels, and maps.

Therefore, you can't always return nil if the return type is a type parameter. You may return nil in other circumstances:

  • if the return type is a pointer to a type parameter, but that's not T, it's *T.
func GetG[T Object]() *T {
    return nil // ok
}
  • if you can assign nil to all types in the type parameter's type set — identified by its constraint:
// type set comprises only *int and *string
func GetG[T *int | *string]() T {
    return nil // ok
}

The case above is mentioned by the language spec in Assignability:

> A value x of type V is assignable to a variable of type T [...] if
>
> [...]
> - x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T's type set.

With a basic interface (methods only) as a constraint, this condition doesn't hold for the reason explained above: you can satisfy it with types which nil isn't assignable to.

Otherwise what you can do is to return T's zero value, which you can do in several different ways, the most straightforward one is:

func GetG[T Object]() T {
    var zero T
    return zero
}

huangapple
  • 本文由 发表于 2023年3月17日 06:32:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75762212.html
匿名

发表评论

匿名网友

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

确定