根据类型断言等返回参数的数量更改行为

时间:2017-01-23 21:22:51

标签: go

我一直在学习Go,有一件事让我特别感兴趣的是类型断言的行为根据捕获的返回值的变化而变化的方式:

var i interface{} = "hello"

val, ok := i.(int) // All good
fmt.Println(val, ok)

val = i.(int) // Panics
fmt.Println(val)

这感觉就像一个对用户定义的函数非常有用的模式。用户要么必须明确地获得" ok"第二个返回值或使用下划线忽略它。在任何一种情况下,他们都明确表示他们已经意识到该功能可能会失败。然而,如果他们只获得一个返回值,它可能会默默地失败。因此,如果用户没有检查错误,那么恐慌或类似似乎是合理的(如果错误应该是"永远不会发生,这将是合理的)。我假设语言开发人员在使类型断言以这种方式工作时背后的逻辑。

但是当我试图找出如何做到这一点时,我一无所获。我知道类型断言不是一个实际的功能。许多具有多个返回值的语言无法检查实际使用的返回值的数量(MATLAB是我唯一知道的),但是再次,大多数语言都不会使用行为就像类型断言演示一样。

那么,它是否可能,如果可能,怎么样?如果没有,是否有一个特殊的原因,尽管内置类型的断言可以解除这种行为?

2 个答案:

答案 0 :(得分:1)

可悲的是,它们无法用于正常功能。据我所知只有类型断言,映射值访问和范围允许它。

通常当你想要一个带有一个的函数和一个可选的第二个错误参数时,你可以将它们命名为

func DoSomething() (string, error) {...} // i will return an error
func MustDoSomething() string {...}  // i will panic

一个例子是https://golang.org/pkg/regexp/#MustCompile

答案 1 :(得分:1)

这个答案:@christian的https://stackoverflow.com/a/41816171/10278提供了关于如何模拟“结果计数超载”模式的最佳实践建议。

我的目的是解决问题的另一部分:这一部分:“但是,当我试图找出可以怎么做的时候,我什么也没发现。”

下面说明了Go类型断言的实现方式。


Go中类型断言的调用表现为,就像它们根据结果数量重载了一样。

现在,转到does not support overloading方法和运算符。

看一下Go的实现,这是根据结果数量看来类型断言似乎过载的原因:

  • Go 编译器提供了这些内置操作所特有的特殊处理。

这种特殊的分派发生在类型断言的内置概念上,因为编译器正在雕刻出 not 对非内置代码可用的特殊逻辑。

Go编译器和运行时用Go编写。这让我(某种程度上)很容易发现编译器是解释此行为的关键。

看看编译器的这一部分:

代码注释已经揭示了很多:

// dottype generates SSA for a type assertion node.
// commaok indicates whether to panic or return a bool.
// If commaok is false, resok will be nil.

我们可以使用调试器来逐步完成一些类型声明代码。

this playground snippet为例。具体来说,这些行:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

(如果您使用build Go from source,则)如果您使用调试器进入第一个类型断言,您将在Go运行时中获得以下代码:

如果您进入第二个,您将到达:

在第438行,您看到func assertI2I(具有单个返回值)。在行454下方,您会看到assertI2I2。请注意,这两个函数的名称几乎相同,但不完全相同!

第二个函数的名称末尾有一个2。该函数还具有两个返回的结果

如我们所料:

(请查看iface.go中的函数体,并注意其中包含panic。)

assertI2IassertI2I2遵守我们期望的重载规则。如果它们仅在结果数量上有所不同,那么由于诸如“ assertI2I重新声明”之类的编译器错误,我们这些从源代码编译Go的人将无法编译Go运行时。

使用该语言的用户通常并不了解这些内置的运行时函数,因此从表面上看,两行代码似乎都调用同一函数:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

但是,在编译时,编译器根据是否发现大小写“ commaok”来分支:

我们自己的最终用户代码无法修改Go的lexing / parsing / AST-walking以便基于“ commaok”调度我们的函数的不同样式。

无论好坏,这就是为什么用户编写的代码无法利用这种模式的原因。