【问题标题】:Changing behavior based on number of return arguments like type assertions根据类型断言等返回参数的数量改变行为
【发布时间】:2017-01-23 21:22:50
【问题描述】:

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

var i interface{} = "hello"

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

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

这感觉像是一种对用户定义的函数非常有用的模式。用户要么必须显式获取“ok”第二个返回值,要么使用下划线忽略它。无论哪种情况,他们都明确表示他们知道该功能可能会失败。而如果他们只得到一个返回值,它可能会默默地失败。因此,如果用户没有检查错误,恐慌或类似情况似乎是合理的(如果错误“永远不会”发生,这将是合理的)。我认为这就是语言开发人员使类型断言以这种方式工作的逻辑。

但是当我试图找出如何做到这一点时,我什么也没找到。我知道类型断言不是一个实际的函数。并且许多具有多个返回值的语言无法检查实际使用了多少返回值(MATLAB 是我所知道的唯一一个),但话又说回来,其中大多数不使用类型断言所展示的行为。

那么,有可能吗?如果可以,怎么做?如果不是,是否有特殊原因排除了这种行为,尽管它可以通过内置类型断言来实现?

【问题讨论】:

  • 您无法编译分配错误数量的返回值的代码,所以我不确定您的建议是什么。错误不是“不应该发生”的情况,即使在正常情况下,错误也会一直返回和处理。

标签: go


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    我的目标是解决问题的另一部分——这部分:“但是当我试图找出如何做到这一点时,我什么也没找到”。

    下面解释了 Go 类型断言是如何完成的。


    Go 中类型断言的调用行为就好像它们基于结果的数量而被重载

    然而,去does not support overloading 的方法和运算符。

    查看 Go 的实现,以下是基于结果数量的类型断言似乎被重载的原因:

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

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

    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 遵守我们期望的重载规则。如果它们仅在结果数量上有所不同,那么我们这些从源代码编译 Go 的人将无法编译 Go 运行时,因为诸如“assertI2I redeclared”之类的编译器错误。

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

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

    但是,在编译时,编译器会根据是否找到“commaok”的情况进行分支:

    我们自己的最终用户代码无法修改 Go 的 lexing/parsing/AST-walking 以根据“commaok”调度我们的不同风格的函数。

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

    【讨论】:

      猜你喜欢
      • 2020-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-28
      • 1970-01-01
      • 2020-06-06
      • 2021-12-09
      相关资源
      最近更新 更多