返回泛型类型的默认值

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

Return default value for generic type

问题

如何在泛型类型 T 中返回 nil

func (list *mylist[T]) pop() T {
	if list.first != nil {
		data := list.first.data
        list.first = list.first.next
		return data
	}
	return nil
}

func (list *mylist[T]) getfirst() T {
	if list.first != nil {
		return list.first.data
	}
	return nil
}

我得到了以下编译错误:

无法在返回语句中使用 nil 作为 T 类型的值
英文:

How do you return nil for a generic type T?

func (list *mylist[T]) pop() T {
	if list.first != nil {
		data := list.first.data
        list.first = list.first.next
		return data
	}
	return nil
}

func (list *mylist[T]) getfirst() T {
	if list.first != nil {
		return list.first.data
	}
	return nil
}

I get the following compilation error:

 cannot use nil as T value in return statement

答案1

得分: 70

你不能对于任何类型返回nil。例如,如果将int用作T的类型参数,返回nil是没有意义的。对于结构体来说,nil也不是一个有效的值。

你可以做的是返回用于T类型参数的零值,这是有意义的。例如,对于指针和切片来说,零值是nil,对于string来说是空字符串,对于整数和浮点数来说是0

如何返回零值?只需声明一个T类型的变量,并返回它:

func getZero[T any]() T {
    var result T
    return result
}

测试一下:

i := getZero[int]()
fmt.Printf("%T %v\n", i, i)

s := getZero[string]()
fmt.Printf("%T %q\n", s, s)

p := getZero[image.Point]()
fmt.Printf("%T %v\n", p, p)

f := getZero[*float64]()
fmt.Printf("%T %v\n", f, f)

输出结果为(在Go Playground上尝试一下):

int 0
string ""
image.Point (0,0)
*float64 <nil>
英文:

You can't return nil for any type. If int is used as the type argument for T for example, returning nil makes no sense. nil is also not a valid value for structs.

What you may do–and what makes sense–is return the zero value for the type argument used for T. For example the zero value is nil for pointers, slices, it's the empty string for string and 0 for integer and floating point numbers.

How to return the zero value? Simply declare a variable of type T, and return it:

func getZero[T any]() T {
	var result T
	return result
}

Testing it:

i := getZero[int]()
fmt.Printf(&quot;%T %v\n&quot;, i, i)

s := getZero[string]()
fmt.Printf(&quot;%T %q\n&quot;, s, s)

p := getZero[image.Point]()
fmt.Printf(&quot;%T %v\n&quot;, p, p)

f := getZero[*float64]()
fmt.Printf(&quot;%T %v\n&quot;, f, f)

Which outputs (try it on the Go Playground):

int 0
string &quot;&quot;
image.Point (0,0)
*float64 &lt;nil&gt;

答案2

得分: 63

*new(T) 习惯用法

这被建议作为 golang-nuts 中首选的选项。虽然可读性可能较差,但如果/当某个零值内置类型被添加到语言中时,更容易找到并替换。

它还允许一行代码完成赋值。

new 内置函数 为任何类型的变量分配存储空间并返回指向它的指针,因此对 *new(T) 解引用实际上得到了 T 的零值。你可以使用类型参数作为参数:

func Zero[T any]() T {
    return *new(T)
}

如果 T 是可比较的,这在检查某个变量是否为零值时非常方便:

func IsZero[T comparable](v T) bool {
    return v == *new(T)
}

类型为 Tvar

直接明了,阅读起来更容易,尽管总是需要多一行代码:

func Zero[T any]() T {
    var zero T
    return zero
}

命名返回类型

如果你不想显式声明一个变量,可以使用命名返回。虽然不是每个人都喜欢这种语法,但在函数体比这个假设的例子更复杂,或者如果你需要在 defer 语句中操作该值时,这可能会很方便:

func Zero[T any]() (ret T) {
    return
}

func main() {
    fmt.Println(Zero[int]())               // 0
    fmt.Println(Zero[map[string]int]())    // map[]  
    fmt.Println(Zero[chan chan uint64]())  // <nil>
}

命名返回的语法与变量声明的语法非常相似,这并非偶然。

使用你的例子:

func (list *mylist[T]) pop() (data T) {
    if list.first != nil {
        data = list.first.data
        list.first = list.first.next
    }
    return
}

对于不可为 nil 的类型返回 nil

如果你确实想要这样做,如你在问题中所述,你可以显式地返回 *T

当类型参数 T 受限于不包括指针类型的某些约束时,可以将返回类型声明为 *T,现在你可以返回 nil,它是指针类型的零值。

// 约束仅包括非指针类型
func getNilFor[T constraints.Integer]() *T {	
    return nil
}

func main() {
    fmt.Println(reflect.TypeOf(getNilFor[int]()))    // *int
    fmt.Println(reflect.TypeOf(getNilFor[uint64]())) // *uint64
}

让我再次强调:当 T 受限于任何允许指针类型的约束时,这种方法效果最好,否则你得到的是指向指针的指针类型:

// 注意这一点
func zero[T any]() *T {
    return nil
}

func main() {
    fmt.Println(reflect.TypeOf(zero[int]()))  // *int,好的
    fmt.Println(reflect.TypeOf(zero[*int]())) // **int,也许不是你想要的...
}
英文:

The *new(T) idiom

This has been suggested as the preferred option in golang-nuts. It is probably less readable but easier to find and replace if/when some zero-value builtin gets added to the language.

It also allows one-line assignments.

The new built-in allocates storage for a variable of any type and returns a pointer to it, so dereferencing *new(T) effectively yields the zero value for T. You can use a type parameter as the argument:

func Zero[T any]() T {
    return *new(T)
}

In case T is comparable, this comes in handy to check if some variable is a zero value:

func IsZero[T comparable](v T) bool {
    return v == *new(T)
}

var of type T

Straightforward and easier to read, though it always requires one line more:

func Zero[T any]() T {
    var zero T
    return zero
}

Named return types

If you don't want to explicitly declare a variable you can use named returns. Not everyone is fond of this syntax, though this might come in handy when your function body is more complex than this contrived example, or if you need to manipulate the value in a defer statement:

func Zero[T any]() (ret T) {
	return
}

func main() {
	fmt.Println(Zero[int]())               // 0
	fmt.Println(Zero[map[string]int]())    // map[]  
	fmt.Println(Zero[chan chan uint64]())  // &lt;nil&gt;
}

It's not a chance that the syntax for named returns closely resembles that of var declarations.

Using your example:

func (list *mylist[T]) pop() (data T) {
    if list.first != nil {
        data = list.first.data
        list.first = list.first.next
    }
    return
}

<hr>

Return nil for non-nillable types

If you actually want to do this, as stated in your question, you can return *T explicitly.

This can be done when the type param T is constrained to something that excludes pointer types. In that case, you can declare the return type as *T and now you can return nil, which is the zero value of pointer types.

// constraint includes only non-pointer types
func getNilFor[T constraints.Integer]() *T {	
	return nil
}

func main() {
	fmt.Println(reflect.TypeOf(getNilFor[int]()))    // *int
	fmt.Println(reflect.TypeOf(getNilFor[uint64]())) // *uint64
}

Let me state this again: this works best when T is NOT constrained to anything that admits pointer types, otherwise what you get is a pointer-to-pointer type:

// pay attention to this
func zero[T any]() *T {
	return nil
}

func main() {
	fmt.Println(reflect.TypeOf(zero[int]()))  // *int, good
	fmt.Println(reflect.TypeOf(zero[*int]())) // **int, maybe not what you want...
}

答案3

得分: 0

你可以初始化一个空变量。

if l == 0 {
    var empty T
    return empty, errors.New("empty Stack")
}

请注意,这是一个示例代码片段,其中的变量类型 T 需要根据实际情况进行替换。

英文:

You can init a empty variable.

if l == 0 {
        var empty T
        return empty, errors.New(&quot;empty Stack&quot;)
    }

huangapple
  • 本文由 发表于 2022年1月5日 06:39:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/70585852.html
匿名

发表评论

匿名网友

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

确定