go - Go 的 `nil` 类型的幕后发生了什么?
问题描述
我最近在一个流行的编程论坛上读到,Go 支持一些“无类型”值——特别是nil
,Go 的 null 值/底部类型。我在 Go 方面的经验相当有限,这个论坛上的一个声明让我措手不及——即x := nil
用 Go 编写是非法的。
果然,带有该行的玩具 Go 程序无法编译,编译器错误清楚地指出论坛中的评论已签出:nil
不允许声明和分配变量 to。这似乎有点奇怪的限制,但事情变得奇怪了。
通过从函数返回元组来返回部分错误是 Go 中的一个常见习惯用法。像这样的东西:
func might_error() (int, error) {
return 1, nil
}
func main() int {
x, err := might_error()
if err != nil {
panic("err was not nil!")
}
return x
}
据我所知,这至少有两个不一致之处:
首先,即使
nil
在纸面上是无类型的,它也采用error
类型(通过它实现Error
与 Go 的鸭子类型相结合的接口)以符合might_error
的返回类型签名。其次,它似乎
nil
被用于以前非法的声明和赋值main
,并且在这种情况下(至少在可比较的语言中)might_error
可以被视为 constexpr。- 更奇怪的是,
x, err := might_error()
用!x, err := 1, nil
use of untyped nil
- 更奇怪的是,
我目前的想法是,在函数的类型签名需要它的情况下,Error
接口被注入到特定实例中nil
,这意味着它不再是无类型 的,而是在该特定实例的生命周期内nil
变成有类型 的,但我不是完全可以肯定这是正确的,因为从本质上讲,这似乎是一种奇怪的设计选择,但无法明确概括。nil
nil
是什么促使了这些设计选择?为什么nil
要无类型而不是使其成为正确的空类型,除非方便,此时它成为实现Error
接口的类型化值(据我所知)?
解决方案
从某种意义上说,这是一个完全没有问题的问题,实际上,如果将 nil 值存储在使该变量为非 nil 的接口变量中,则可能会造成混淆。这是https://golang.org/doc/faq#nil_error并且每个人都被这个问题困扰了几次,直到您了解到包含 nil 变量的接口值本身不再是 nil 。这有点像var s = []*int{nil, nil, nil}
wheres
只包含 nil 而不是 nil 本身。
从技术上讲(从语言设计的角度来看),您可以引入几个“nil”,例如nil
指针、null
接口、noop
函数和vac
通道。(有点夸张)。有了这个,你可以:
type T whatever
func (t *T) Error() string // make *T implement error
var err error // interface variable of type error
print(err == null) // true, null is Zero-Value of interface types
print(err == nil) // COMPILER ERROR, cannot compare interface to nil (which is for pointers only
var t *T = nil // a nil pointer to T
err = t // store nil *T in err
print(err == null) // false err is no longer null, contains t
您甚至可以删除编译器错误:
err = t // store nil *T in err
print(err == null) // false, err contains t
print(err == nil) // "deep" comparison yielding true
err = &T{} // store non-nil *T in err
print(err == null) // still false
print(err == nil) // false, value inside err is no longer nil
您还可以为 nil 定义一个默认类型,例如*[]chan*func()string
,这样您就可以x := nil
像f := 3.141
在无类型常量 3.141 的默认类型是 float64 的情况下那样编写。但是这种 nil 的默认类型是任意的并且根本没有帮助(如我的示例所示;*[]chan*func()string
不常见)。
如果我没记错的话,在 golang-nuts 邮件列表上有一个关于这个主题的更长的讨论,其中讨论了这个设计的合理性。它归结为:“具有nil
多重含义而不是常数的实际现实生活问题很小(基本上只是包含 nil 不是 nil 的错误类型的变体)。这个小问题的“解决方案”将使语言变得相当复杂(例如,通过为接口类型的零值引入null
文字)。与为接口类型引入类型化的 nil 或 null 相比,教人们包含 nil 的接口值本身不再是 nil 可能更简单。
在 10 多年的 Go 编程中,我从字面上根本不需要考虑nil
文字是有类型的、无类型的、常量或其他什么。您可能所指的文章是构建纯粹的学术问题,但实际上在实践中没有问题的问题来自一个微小的设计决策,即nil
指针、切片、映射、通道和函数类型的所有零值只有一个文字。
附录
首先,即使 nil 在纸面上是无类型的,它也采用错误类型(通过它实现 Error 接口并结合 Go 的鸭子类型),以符合 may_error 的返回类型签名。
这是对所发生事情的完全错误的描述。
如果你写
func f() (r int) { return 7 }
然后将 7 分配给 int 类型的 r 并返回 f。这是可行的,因为 7 可以分配给 int。
在
func might_error() (int, error) { return 1, nil }
同样的情况,错误类型(接口类型)的第二个返回变量设置为 nil,因为 nil 可以分配给任何接口类型,就像您可以将 nil 分配给任何指针类型或任何函数类型一样。
这与“结合 Go 的鸭子类型实现 Error 接口”无关。绝对不。nil 根本不实现错误接口。任何接口值都可以是 nil,就像可以是任何函数值或切片值或通道值一样。将 chan 设置为 nil 基本上“清除”了通道变量,这并不意味着 nil 以某种方式“实现了通道接口”。您似乎将几种类型的零值以及如何通过将 nil 分配给实现接口来设置它们。所有这些基本上与是否输入 nil 无关。源代码 os 中的nil
文字重载,通常可以被认为只是表示类型的零值。
推荐阅读
- javascript - 如何从 Firebase 获取 displayName?
- bash - 使用fish shell一次创建多个文件
- android - getCurrentUser() 不给 null
- c++ - 请解释 `function1(p1,p2,p3);` 的输出
- angular - Angular 6 ng-select2 从 api 填充
- c++ - 指针和指针的地址如何共享同一个内存地址?
- php - Laravel 中间件不会在每个请求之前运行
- python - 需要设计模式建议
- sql - SQL TRIM() 函数中的奇怪错误
- rust - How to pattern match on values inside a type implementing Deref, such as Box, without copying the contents?