Go语言中的类型断言(type assertion)的设计背后的原因是什么?

huangapple go评论86阅读模式
英文:

What is the reasoning behind the way Go's does "type assertion"?

问题

我正在努力理解Golang的类型断言只适用于显式定义为接口类型的变量,而不适用于具体类型(例如"string"、"int32"等)的事实。

以下是一个简单的代码示例,说明了我困惑的根本原因:

package main

import "fmt"

// 这里我们定义了一个接口类型:
type Shape interface {
	DoubleSize() int32
}

// 这里我们定义了一个实际上只是int32的新类型:
type Rect int32

// 通过实现该接口的方法,使"Rect"类型与"Shape"接口相符
// 由于Go中的接口是隐式实现的,这将使"Rect"类型成为"Shape"接口的隐式实例
func (theShape Rect) DoubleSize() int32 {
	return int32(theShape) * 2
}

// 这个函数期望其"someShape"参数是"Shape"类型(或者"像Shape接口一样的类型")
func whateverFunction(someShape Shape) int32 {
	return someShape.DoubleSize()
}

func main() {
	var newRect = Rect(5)
    // ^^ 如果改为"var newRect Shape = Rect(5)",则不会出现类型断言错误

	whateverFunction(newRect) // 这个函数可以正常工作,因为"newRect"隐式实现了这个函数期望接收的"Shape"接口。

    // !! 但是类型断言在"newRect"上不起作用
	v, ok := newRect.(Shape) // 错误:无效操作:newRect(类型为Rect的变量)不是一个接口
	if !ok {
		fmt.Println("类型断言失败")
		return
	}
	fmt.Println("这是v:", v)
}

正如这个问题的标题所暗示的,我无法理解为什么只有在接口类型上实现类型断言,并检查分配给实现该接口的变量的底层值是否与我们在".(T)"断言方法中指定的值相符。这让我觉得"类型断言"是一个无意中的误称,它暗示它适用于所有类型,但实际上只适用于接口类型。

我的意思是,显然在这个语言设计决策背后肯定有一个原因,我认为这可能与惯用的Golang编写方式有关,但是尽管我看过很多关于这个问题的资源,它们从来没有指明那个原因。

对我来说,有道理的原因是,Go程序应该"最好(我假设,因为显式接口定义是可选的)"使用所有变量来表示某种接口(行为),因此在变量上定义一个显式接口在清晰性和可读性方面是有意义的。

但正如我提到的,我从来没有看到任何资源明确说明这是为什么"类型断言"功能在Go中是如何实现的,我希望你能帮助我澄清这个困惑。

-- 更新1 - 添加一点来澄清我的问题:

本质上,我认为我的问题是关于为什么类型断言只在变量的接口被显式实现时才起作用,而在接口被隐式实现时不起作用的原因(我不理解)。

正如"whateverFunction"所示,代码确实认为"newRect"实现了"Shape"接口,或者说"是"Shape"接口的实现"(否则该函数将无法使用该变量,但它确实可以);但是类型断言".(T)"方法的代码并不认为"newRect"是"Shape"接口的实现。

因此,如果在Golang中对于什么是接口的实现有区分,我认为这背后必定有一个设计决策的原因(为了区分)。

这就是为什么我提到到目前为止,我能想到的唯一原因是,如果这是一种让人们以某种方式编写Go代码的方式的方法。

英文:

I'm trying to wrap my head around the fact that Golang's type assertion only works for the variables that are explicitly defined to be of an interface type and doesn't work for concrete types (i.e. "string", "int32", etc..).

Here's a quick and simple code sample that illustrates the root cause of my confusion:

package main

import "fmt"

// here we define an interface type:
type Shape interface {
	DoubleSize() int32
}

// here we define a new type which is really just an int32:
type Rect int32

// here we make "Rect" type above comply with the "Shape" interface by implementing the methods of that interface
// and so, since the interfaces in Go are implemented implicitly, this should make the "Rect" type an implicit instance of the "Shape" interface
func (theShape Rect) DoubleSize() int32 {
	return int32(theShape) * 2
}

// this function expects its "someShape" parameter to be of "Shape" type (or "the type that quacks like "Shape" interface does))
func whateverFunction(someShape Shape) int32 {
	return someShape.DoubleSize()
}

func main() {
	var newRect = Rect(5)
    // ^^ if this is instead written as "var newRect Shape = Rect(5)" no error with type assertion happens down the line

	whateverFunction(newRect) // the function works just fine because "newRect" implicitly implements the "Shape" interface — the one that this function expects to receive.  

    // !! but type assertion doesn't work on "newRect"
	v, ok := newRect.(Shape) // error: invalid operation: newRect (variable of type Rect) is not an interface
	if !ok {
		fmt.Println("type assertion failed")
		return
	}
	fmt.Println("This is v:", v)
}

As the title of this question suggests, I can't understand the reasoning behind implementing type assertion to only work on interface types, and check if the underlying value assigned to the variable that implements that interface EXPLICITLY is what we specify inside of ".(T)" assertion method. This makes me feel like "type assertion" is an unintentional misnomer that implies that it works for all types but doesn't, only for interface types.

I mean, there obviously must be a reason behind this language design decision, and I think it might have something to do with the way idiomatic Golang is meant to be written, but though I have seen plenty of resources on this matter, they never specify that reason.

The reason that would make some sense to me is if the Go program should, "preferably (I assume, since explicit interface definition is optional)", be written with all variables representing some interface (behavior), and therefore defining an explicit interface on a variable makes sense for the purposes of clarity and readability.

But as I mentioned, I have never actually seen any resources specify why this is how the "type assertion" functionality is implemented in Go, and I am hoping you could help me clarify this confusion.

-- upd 1 - Adding a bit to clarify my question:

At it's core, I think, my question is about the reason (that I do not understand) type assertion only works when interface of the variable is implemented explicitly but not when the the interface is implemented implicitly.

As demonstrated by the "whateverFunction", the code does consider "newRect" to implement the "Shape" interface, or "be the implementation of the "Shape" interface" (otherwise the function wouldn't work with that variable, but it does), but the code behind type assertion ".(T)" method doesn't consider "newRect" to be the implementation of the "Shape" interface.

Therefore, if there is a differentiation in Golang on what to consider an implementation of the interface, I figured there must then be a reason behind such a design decision (to differentiate).

And that is why I mentioned that the only reason so far that I could think of is if this was a way to make people write Go code in a certain way.

答案1

得分: 3

类型断言用于检查接口中包含的值的类型。它的定义如下:

https://go.dev/ref/spec#Type_assertions

这是因为接口包含两个值:值的类型和实际值,并且检查接口中包含的值是否属于某种类型是在运行时进行的操作。

对于非接口值,类型断言是未定义的,因为这种值的类型已经知道,而且它不能是另一种类型。

您可以使用类型断言来检查一个接口是否实现了另一个接口。这意味着接口中包含的具体值也实现了类型断言的接口。

现在,回到您要做的事情:

v, ok := newRect.(Shape)

这可以简化为:

v := Shape(newRect)

因为如果newRect没有实现Shape接口,这个表达式将在编译时出错,而不是在运行时检测到类型断言。

英文:

Type assertion checks the type of the value contained in an interface. That is the way it is defined:

https://go.dev/ref/spec#Type_assertions

This is because an interface contains two values: the type of the value, and the actual value, and checking if the value contained in an interface is of a certain type is a runtime operation.

Type assertion is not defined for a non-interface value, because the type of such value is already known, and it cannot be another type.

You can use type-assertion to check if an interface implements another interface. That means, the concrete value contained in the interface also implements the type-asserted interface.

Now, back to what you are trying to do:

v, ok := newRect.(Shape)

This is simply:

v:=Shape(newRect)

Because if newRect does not implement Shape interface, this expression is a compile-time error, as opposed to the runtime detection of a type-assertion.

答案2

得分: 0

你可以查看Burak Serdar的答案 - 你可能会发现它更简洁和有帮助。尽管如此,我将发布整个推理链,最终让我恍然大悟:

|-> 当我们无法确定我们期望接收的数据的确切类型时(例如,由于用户的输入,在程序中的某个函数中,同一个参数可能接收不同类型的数据),我们使用接口,但我们知道所提供数据应该具有的确切行为。

> ^^ 因此,可以得出结论,在编译时,将存储在接口中的值的实际类型是未知的。(否则,显然,我们会在代码中直接指定它。)

| -> 因此,我们提供了类型断言,以便能够根据我们在程序执行期间期望程序提供的可能值来定义程序的行为。

|-> 因此,类型断言仅适用于明确指定为接口类型的变量,而不适用于可能实现相同接口但未明确指定为接口类型的变量

原因是

我们只在运行时需要这种类型断言,当我们使用接口时,因为我们不知道将发送到程序的数据的确切类型 - 这只是在使用接口时才会出现的必要性,因此类型断言仅适用于明确指定为接口类型的类型,因为在所有其他情况下,数据类型是已知的(允许编译器通过变量隐式地假设接口的实现 - 因为它已经知道所涉及数据的所有数据类型),而我们根本不需要使用已知类型的数据的类型断言。

英文:

You can check out Burak Serdar's answer - you might find it more concise and helpful. Nevertheless, I will post the entire chain of reasoning that made it finally *click* for me:

|-> Interfaces are used when we cannot be certain of the exact type of the data that we are expecting to receive (because, for example, as a result of user's input, the same parameter in some one function in a program might receive data of different types), but we know the exact behavior that the provided data should have.

> ^^ So it follows that the actual type of the value that will be stored in an interface is unknown at compile time. (Otherwise, obviously, we would've just specified it right there in the code.)

| -> Therefore we are given type assertion to be able to define program's behavior based on a possible values that we expect a program will be provided with during its execution.

|-> Therefore the reason why type assertion works only on variables explicitly specified to be of an interface type and not on those that might implement the same interface but are not explicitly specified to be of an interface type

is because

we only ever need such type assertion during runtime, when we use interfaces because we don't know the exact type of data that will be sent to the program - it's a necessity that comes up only when using the interfaces therefore type assertion only works for types explicitly specified to be interface types, because in all other cases the type of data is known (allowing compiler to assume the implementation of an interface by a variable implicitely - because it already knows all the data types of the data involved) and we simply never need to use type assertion with the data the type of which is already known.

huangapple
  • 本文由 发表于 2023年4月4日 01:36:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/75922255.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定