在Golang中将接口设置为nil

7
我正在尝试将接口的内部值设置为nil,类似以下内容:
typ := &TYP{InternalState: "filled"}
setNil(typ)

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

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

更多细节请参见此代码在play.golang.org中


但是为什么呢?你不能使用布尔值吗? - Yannick
@yene:我喜欢保持界面设计的整洁性。 - Mohsen
我不知道如果不直接从函数返回nil,该怎么做。例如,考虑一下 setNil(3) - julienc
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>

所以这样就可以了。

使用unsafe函数将指针置为空;

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

我们想要接受指向指针变量的地址(指针),这类似于**SomeType。为了实际上能够给指针变量赋新值(nil),我们必须对其进行解引用(*运算符)。但是unsafe.Pointer无法进行解引用,因此首先我们必须将其转换为指向指向“某物”的指针,但由于我们只想要赋值nil(不管指向值的类型如何都相同),所以并不重要,“某物”我会使用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 函数使指针为 nil

你也可以只使用反射(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

感谢icza,但是我想避免客户知道unsafe.Pointer的存在,并且我也不希望客户将类型作为*interface{}传递。你能实现这个吗?=> http://play.golang.org/p/KhR4-BHMPW - Mohsen
1
@Mohsen 请查看编辑后的答案。我添加了第三种方法,只在函数内部使用反射。您必须传递变量的地址,这是无法避免的,但这种方法不使用“unsafe.Pointer”。 - icza
非常感谢icza,但我必须看一下:http://play.golang.org/p/aPslX_vgiJ(为什么Go没有泛型!):-) - Mohsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接