Golang结构体的可赋值性和类型标识

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

Golang struct assignability and type identity

问题

我有以下两个结构体:

    func main() {
    	type A struct {
    		v int
    	}
    	type B struct {
    		v int
    	}
    	var b B = A{}
    }

赋值语句var b B = A{}会失败,并显示错误信息:

无法将类型为'A{}'的A用作类型B

但是在golang规范:类型标识中,它说:

如果两个结构体类型具有相同的字段顺序,并且相应的字段具有相同的名称、相同的类型和相同的标签,则它们是相同的。来自不同包的非导出字段名称始终是不同的。

所以,我期望类型AB是相同的。在规范:可赋值性中,它说:

如果满足以下条件之一,则值x可以赋值给类型为T的变量("x可以赋值给T"):

  • x的类型与T相同。

根据所有这些信息,我倾向于认为赋值应该成功,因为A{}的类型与B是相同的,但显然我漏掉了一些东西。

那么,我在理解错误消息的根本原因方面漏掉了什么?

英文:

I have the following 2 structs:

    func main() {
    	type A struct {
    		v int
    	}
    	type B struct {
    		v int
    	}
    	var b B = A{}
    }

The assignment var b B = A{} fails with an error message:
> Cannot use 'A{}' (type A) as the type B

But in the golang spec:Type identity it says:
> Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. Non-exported field names from different packages are always different.

so, I expect types A and B to be identical. And in the spec:Assignability it says:
> A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:
> - x's type is identical to T.

given all that information, I tend to think that the assignment should be succeeded since the type of A{} is identical to B, but apparently I'm missing something.

So, what am I missing here to understand the root cause of the error message?

答案1

得分: 5

请注意,你指出的定义以以下方式开始:

定义类型始终与任何其他类型不同。

AB 是定义的类型,它们具有不同的名称,因此它们是不同的。类型 struct { v int } 与任何类型 struct {v int} 相同,但在这种情况下,它们不是定义的类型。例如,你可以声明:

func f(x struct {v int})

然后你可以调用:

f(struct {v int}{v:1})

这两个结构体是相等的。

你仍然可以使用以下方式将 AB 赋值:

var b B = B(A{})

这是因为 AB 的底层类型在结构上是相同的。

英文:

Note that the definition you pointed to starts with:

A defined type is always different from any other type.

A and B are defined types, they have different names, so they are different. The type struct { v int } is identical to any type struct {v int}, but in this case, these are not defined types. For instance, you can declare:

func f(x struct {v int})

Then you can call:

f(struct {v int}{v:1})

The two structs are equal.

You can still assign A and B using:

var b B = B(A{})

This works because the underlying types of A and B are structurally identical.

答案2

得分: 3

给定类型 AB 的定义如下:

    type A struct {
        v int
    }

    type B struct {
        v int
    }

这两个类型是否相同?

不相同。根据类型定义

> 类型定义创建一个新的、独立的类型,其底层类型相同。

A 和 B 的底层类型是什么?

它们都是 struct { v int }。根据:

> 如果 T 是预声明的布尔、数值或字符串类型,或者是一个类型字面量,那么对应的底层类型就是 T 本身。

因此,根据类型声明的形式 TypeSpec = AliasDecl | TypeDef .TypeDef = identifier Type .,你可以匹配你的类型并看到 AB 都符合类型定义,其中:

  • AB标识符,以及
  • struct { v int } 是它们的底层结构体 类型字面量

赋值是如何工作的?

根据可赋值性,你可以看到没有适用的条件:

  • x 的类型与 T 相同。AB 不相同,因为它们是不同的定义类型
  • x 的类型 V 和 T 具有相同的底层类型,并且 V 或 T 中至少有一个不是定义类型。 — 它们都是定义类型

因此,类型为 A 的变量不能赋值给类型为 B 的变量,反之亦然。不可赋值性还意味着你不能将类型为 A 的变量传递给类型为 B 的函数参数,因为

> 参数必须是可赋值给 F 的参数类型的单值表达式。

Burak 的答案中的示例 func foo(x struct{ v int }) 可以编译,因为 foo 的参数类型是一个结构体字面量,而不是定义类型,并且符合可赋值性的条件。

转换是如何工作的?

你可以从 A 转换为 B,因为根据转换

> 非常量值 x 可以在以下任何情况下转换为类型 T:
>
> - [...]
> - 忽略结构体标签(见下文),x 的类型和 T 有相同的底层类型

而我们上面证明了这两个结构体确实具有相同的底层类型。因此,以下代码可以编译通过:

var a A = A(B{})

<hr>

Playground: https://play.golang.org/p/UyEYrbUufA5

英文:

So given types A and B defined as:

    type A struct {
        v int
    }

    type B struct {
        v int
    }

Are the types identical?

No. From Type Definitions:

> A type definition creates a new, distinct type with the same underlying type

What are A and B underlying types?

Both are struct { v int }. From

> If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself.

So given that a type declaration is in the form TypeSpec = AliasDecl | TypeDef . and TypeDef = identifier Type . you can pattern-match your types and see that both A and B conform to the type definition where:

  • A and B are the identifiers and
  • struct { v int } is their underlying struct type literal.

How does assignment work?

From Assignability you can see that no condition applies:

  • x's type is identical to T.A and B are not identical because both are different defined types
  • x's type V and T have identical underlying types and at least one of V or T is not a defined type. — both are defined types

So a variable of type A is not assignable to a variable of type B and vice-versa. Non-assignability also means that you can't pass a variable of type A to a function parameter of type B, because:

> arguments must be single-valued expressions assignable to the parameter types of F

The example in Burak's answer with func foo(x struct{ v int }) compiles because foo's param type is a struct literal, not a defined type, and falls under the conditions for assignability.

How does conversion work?

You can convert from A to B because, from Conversion`:

> A non-constant value x can be converted to type T in any of these cases:
>
> - [...]
> - ignoring struct tags (see below), x's type and T have identical underlying types.

And we proved above that both structs do indeed have identical underlying types. Therefore this compiles:

var a A = A(B{})

<hr>

Playground: https://play.golang.org/p/UyEYrbUufA5

huangapple
  • 本文由 发表于 2021年6月23日 13:54:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/68094223.html
匿名

发表评论

匿名网友

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

确定