在Golang中将接口设置为nil。

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

Set an interface to nil in Golang

问题

我正在尝试将接口的内部值设置为nil,类似于以下代码:

typ := &TYP{InternalState: "filled"}
setNil(typ)

fmt.Printf("Expecting that %v to be nil", typ)

我需要知道如何实现setNil(typ interface{})方法。

更多细节请参考play.golang.org上的代码

英文:

I'm trying to set an internal value of an interface to nil something like the following :

typ := &TYP{InternalState: "filled"}
setNil(typ)

fmt.Printf("Expecting that %v to be nil", typ)

And I need to know how to implement the setNil(typ interface{}) method.

For more details see this code in play.golang.org.

答案1

得分: 8

事实上,你没有一个接口值。你有一个指针值,指向一个具体类型的指针。这与接口值不同。

如果你想要改变任意类型变量的值,你必须传递一个指向它的指针。这也包括接口类型的变量,以及指针类型的变量。这是一个非常罕见的情况,其中一个指向interface{}的指针是有意义的(*interface{}),事实上是不可避免的。

但是,如果你的函数期望一个接口,而你传递一个非接口值,一个接口值将会被隐式地创建,并且你只能将这个隐式创建的值设置为nil

所以我们有两种不同的情况:

interface{}置为nil的函数

func setNilIf(v *interface{}) {
	*v = nil
}

使用它:

var i interface{} = "Bob"
fmt.Printf("Before: %v\n", i)
setNilIf(&i)
fmt.Printf("After: %v\n", i)

输出:

Before: Bob
After: <nil>

所以它起作用了。

将指针置为nil的函数;使用unsafe

实际上这是你的情况。我们想要改变指针类型变量的值。为了接受任意类型的指针,我们可以使用unsafe.Pointerunsafe.Pointer是一种语言支持,它是一种特殊的指针类型,可以从任意指针类型转换。

我们想要接受指针变量的地址(指针),这类似于**SomeType。为了能够给指针变量赋予新值(nil),我们必须对它进行解引用(*操作符)。但是unsafe.Pointer不能被解引用,所以我们首先必须将其转换为指向指针的指针,但是由于我们只想要赋值nil(不管指向的值的类型如何,nil都是相同的),所以这个“something”并不重要,我只会使用int,然后将unsafe.Pointer指针值转换为**int

func setNilPtr(p unsafe.Pointer) {
	*(**int)(p) = nil
}

使用它:

typ := &TYP{InternalState: "filled"}
fmt.Printf("Before: %v\n", typ)
setNilPtr(unsafe.Pointer(&typ))
fmt.Printf("After: %v\n", typ)

输出:

Before: &{filled}
After: <nil>

所以这个也起作用了。还有另一种使用反射的方法:

将指针置为nil的函数;使用reflect

你也可以只使用反射(reflect包)将指针置为nil。我们仍然必须传递指针类型变量的地址。注意,在这种情况下,参数的类型将简单地是interface{}。它将包含一个动态类型,比如**SomeType。由于指针的零值是nil,我们可以使用reflect.Zero()获得这样一个值,并使用Value.Set()进行设置:

func setNilPtr2(i interface{}) {
	v := reflect.ValueOf(i)
	v.Elem().Set(reflect.Zero(v.Elem().Type()))
}

使用它:

typ2 := &TYP{InternalState: "filled"}
fmt.Printf("Before: %v\n", typ2)
setNilPtr2(&typ2)
fmt.Printf("After: %v\n", typ2)

输出:

Before: &{filled}
After: <nil>

所以这个也起作用了。在Go Playground上尝试一下这些。


但是说真的:如果你想要将某个东西置为nil,直接赋值nil给它就可以了。不要不必要地复杂化事情。

i = nil
typ = nil
英文:

The thing is you don't have an interface value. You have a pointer value, a pointer to a concrete type. That is not the same as an interface value.

If you want to change the value of a variable of any type, you have to pass a pointer to it. This also includes variables of interface type, and also variables of pointer type. This is one of those very rare cases when a pointer to interface{} makes sense (*interface{}), in fact it's inevitable.

But if your function expects an interface and you pass a non-interface value, an interface value will be created implicitly and you could only nil this implicitly created value.

So we have 2 different / distinct cases:

Function to nil an interface{}

func setNilIf(v *interface{}) {
	*v = nil
}

Using it:

var i interface{} = &quot;Bob&quot;
fmt.Printf(&quot;Before: %v\n&quot;, i)
setNilIf(&amp;i)
fmt.Printf(&quot;After: %v\n&quot;, i)

Output:

Before: Bob
After: &lt;nil&gt;

So it works.

Function to nil a pointer; using unsafe

Actually this is your case. We want to change the value of a variable of pointer type. To accept a pointer to any type, we can use unsafe.Pointer. unsafe.Pointer is a language support, it's a special pointer type which can be converted from and to any pointer type.

We want to accept the address (pointer) of the pointer variable, which is something like **SomeType. To actually be able to assign a new value (nil) to the pointer variable, we have to dereference it (* operator). But unsafe.Pointer cannot be dereferenced, so first we have to convert it to a pointer to pointer to "something", but since we only want to assign nil (which is the same to all pointer types regardless of the type of the pointed value), the "something" doesn't matter, I will just use int, and so I will convert the unsafe.Pointer pointer value to **int.

func setNilPtr(p unsafe.Pointer) {
	*(**int)(p) = nil
}

Using it:

typ := &amp;TYP{InternalState: &quot;filled&quot;}
fmt.Printf(&quot;Before: %v\n&quot;, typ)
setNilPtr(unsafe.Pointer(&amp;typ))
fmt.Printf(&quot;After: %v\n&quot;, typ)

Output:

Before: &amp;{filled}
After: &lt;nil&gt;

So this one also works. There is still another way using reflection:

Function to nil a pointer; using reflect

You can also nil a pointer using reflection only (reflect package). We still have to pass the address of the variable of pointer type. Note that in this case the type of the parameter will simply be interface{}. And it will contain a dynamic type like **SomeType. Since pointers have zero value nil, we can obtain such a value with reflect.Zero() which we will set using Value.Set():

func setNilPtr2(i interface{}) {
	v := reflect.ValueOf(i)
	v.Elem().Set(reflect.Zero(v.Elem().Type()))
}

Using it:

typ2 := &amp;TYP{InternalState: &quot;filled&quot;}
fmt.Printf(&quot;Before: %v\n&quot;, typ2)
setNilPtr2(&amp;typ2)
fmt.Printf(&quot;After: %v\n&quot;, typ2)

Output:

Before: &amp;{filled}
After: &lt;nil&gt;

So this one also works. Try these on the Go Playground.


But seriously: if you want to nil something, assign nil to it. Do not complicate things unnecessarily.

i = nil
typ = nil

huangapple
  • 本文由 发表于 2015年11月21日 17:07:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/33841630.html
匿名

发表评论

匿名网友

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

确定