2 回答
TA贡献1858条经验 获得超8个赞
这是一个完全没有问题的问题,因为在实践中,如果将一个nil值存储在接口变量中,使该变量成为非nil,则可能会造成混淆。这是 https://golang.org/doc/faq#nil_error 每个人都会被这个问题咬几次,直到你了解到包含nil变量的接口值本身不再是nil。这有点像只包含 nils 但本身是 non-nil 的。var s = []*int{nil, nil, nil}s
从技术上讲(从语言设计的角度来看),您可以引入几个“nils”,例如 用于指针、接口、函数和通道。(有点夸张)。有了这个,你可以有:nilnullnoopvac
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 定义一个默认类型,例如 这样你就可以像编写非类型常量 3.141 的默认类型是 float64 一样进行编写。但是,nil的这种默认类型将是任意的,根本没有帮助(如我的示例所示; 不常见)。*[]chan*func()stringx := nilf := 3.141*[]chan*func()string
如果我没记错的话,在golang-nuts mailng列表中有一个关于这个主题的更长的讨论,其中讨论了这个设计的合理性。它归结为:“具有多种含义而不是常量的实际现实生活问题是微小的(基本上只是包含nil的错误类型的变体不是nil)。这个小问题的“解决方案”将使语言复杂化(例如,通过为接口类型的零值引入一个文字)。告诉人们包含 nil 的接口值本身不再是 nil 可能比为接口类型引入类型化的 nils 或 null 更简单。nilnull
在10多年的Go编程中,我实际上从未考虑过一个字面上被类型化或非类型化或常量或其他什么。您可能正在引用的文章正在构建纯粹的学术性,但实际上在实践中没有问题,这是一个微小的设计决策,即指针,切片,映射,通道和函数类型的所有零值只有一个文本。nilnil
补遗
首先,即使 nil 在纸面上是无类型的,它也采用错误类型(通过实现 Error 接口和 Go 的 duck 类型),以便符合 might_error 的返回类型签名。
这是对所发生事情的完全错误的描述。
如果你写
func f() (r int) { return 7 }
则 7 被分配给 int 类型的 r,f 返回。这有效,因为可以将 7 分配给 int。
在
func might_error() (int, error) { return 1, nil }
同样的情况是,error(接口类型)的第二个返回变量设置为 nil,因为 nil 可以分配给任何接口类型,就像您可以将 nil 分配给任何指针类型或任何函数类型一样。
这与“实现错误接口与Go的鸭子类型”无关。绝对不是。nil 根本没有实现错误接口。任何接口值都可以为零,就像可以是任何函数值、切片值或通道值一样。将chan设置为nil基本上“清除”通道变量,这并不意味着nil以某种方式“实现通道接口”。您似乎将几种类型的零值以及如何通过分配nil与实现接口来设置它们混为一谈。所有这些基本上与是否键入nil无关。源代码中的文字 os 重载,通常可以认为只是表示类型的零值。nil
TA贡献1831条经验 获得超10个赞
你的例子不会编译(因为不需要返回任何东西),当你进入一种语言的挑剔和细节领域时,你所有的例子都应该真正起作用。😀main
我目前的想法是,在函数的类型签名需要它的情况下,Error接口被注入到nil的特定实例中......
这不太对。Go 规范告诉我们这是如何工作的:
有三种方法可以从具有结果类型的函数返回值:
返回值可以在“return”语句中显式列出。每个表达式都必须是单值表达式,并且可以分配给函数结果类型的相应元素。[截图示例]
这是您在示例程序中使用的方法(我清理了一下以在Go Playground中编译和运行)。表达式:
return 1, nil
方法:
unnamed_return_variable_1 = 1unnamed_return_variable_2 = nil
其中,两个未命名的返回变量实际上没有这些名称(毕竟它们是未命名的),但确实有类型,这要归功于函数的声明。所以
return 1, nil
根本不像:
x, err := 1, nil
但更像是:
var x int; var err error; x, err = 1, nil
如您所见,这也是非常有效的。
是什么促使了这些设计选择?
为此,你必须问问设计师。
未键入的 0 不会引起这种焦虑。
但是,非类型化常量具有默认类型:0
非类型化常量具有默认类型,该类型是在需要类型化值的上下文中(例如,在短变量声明(例如,没有显式类型)中将常量隐式转换为的类型。非类型化常量的默认类型分别为 、 、 或,具体取决于它是布尔值、符文、整数、浮点数、复数还是字符串常量。
i := 0
bool
rune
int
float64
complex128
string
预先声明的标识符没有类型,甚至没有默认类型。它只是有一系列特殊规则,允许它在各个地方使用。nil
非语言规范的思考方式
虽然这不是它的定义方式(它是由Go规范定义的),但我建议这样考虑这个问题:
预先声明的标识符1 是非类型化的。
nil
存在类型化的 nil 值。任何足够像指针的东西都可以是 nil,也可以是 non-nil。例如,任何变量都可以是指向int-nil的指针,就像未初始化的变量一样。此 nil 不是 nil “关键字”(预先声明的标识符,见脚注);这是一个完全不同的“无”,就像布鲁斯(班纳)是一个与布鲁斯(韦恩)完全不同的人。
*int
接口值由两部分组成:类型和该类型的值。类型可以为零!这是另一种 nil,不同于 nil(未键入的“关键字”)和 nil(某些特定 pointer-y 类型的某些特定 nil)。如果类型为 nil,则该值也可以为 nil。如果两者都为 nil,则接口值比较等于 <nil,nil>对,当需要与接口值进行比较时,未键入的“关键字”nil 会变成该对。如果任何一个为非零,则比较表明它们不相等。硬件实现是接口的两个部分都是零:如果任何一个部分为非零,则整个事物为非零,因此“不是零”。
当上下文提供了足够的信息时,将从 nil-the-“关键字”(预先声明的标识符)到适当的硬件样式零值进行转换。分配给某个变量(甚至是未命名的变量)可提供上下文。分配给某些位置参数可提供上下文。尝试使用简短声明无法提供上下文,因此会出现错误。
1 个一般来说,Go更喜欢预先声明的标识符到关键字,因此等等实际上并不是关键字。这也排除了关键字集。尽管如此,有些东西——比如布尔常量和,或者预先声明的标识符的神奇行为,只能通过不覆盖预先声明的标识符然后使用它来访问。也就是说,如果您命名某些内容,则在该名称超出范围之前,您将无法获得iota样式功能。这些事情的行为方式通常通过其他语言的关键字来处理。如果你倾向于思考,比如说,C++或Java,你可能想不断提醒自己,这些不是关键字,即使它们闻起来像它们。false
true
nil
false
true
iota
iota
- 2 回答
- 0 关注
- 117 浏览
添加回答
举报