Golang中类型定义令人意外的赋值行为解析

Golang中类型定义令人意外的赋值行为解析 根据Go语言规范中关于类型定义的描述:

新类型被称为定义类型。它不同于任何其他类型,包括创建它的源类型。

同时

定义类型总是不同于任何其他类型。

我原本期望,如果我有一个类型定义 Foo 的变量,那么只有 Foo 类型的值可以赋值给它,而底层类型或任何其他类型的值永远不行。 这是使用类型定义的主要原因之一,即我不想接受底层类型的任意值,因为这很可能是调用方存在错误。

但在Go中似乎并非如此。请看以下示例,我有一个底层类型为 string 的类型 Foo。当字符串以字面量形式传递时,编译器允许我传入任何字符串(但如果传递的值已经是 string 类型则不行?这似乎有些随意)。

package main

func main() {
  type Foo string
  f := func(t Foo) {}
  
  f(Foo("abc")) // 正常工作,符合预期。

  f("abc") // 意外地工作。预期会出现类型错误。

  var s = "abc"
  f(s) // 符合预期的类型错误
}

这肯定不是一个特性吧?如果是的话,这种行为似乎与规范不符。


更多关于Golang中类型定义令人意外的赋值行为解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

我明白了,我之前从未听说过“无类型常量”这个术语,而且允许这种情况发生实在令人遗憾 😛 感谢您的回复!

更多关于Golang中类型定义令人意外的赋值行为解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,@auste,请查阅规范中关于常量的章节。Jayts和我在另一个问题这里专门解释了数值常量,但我的回答中也有一个使用字符串的例子,这个例子特别适用于你的情况。

有一些变通方法。如果你不希望字符串字面量能够作为 Foo 类型使用,可以将 Foo 转换为结构体:https://play.golang.org/p/cVhNn33Wt4t

package main

import (
	"fmt"
)

type Foo struct {
	s string
}

func (f Foo) String() string {
	return f.s
}

func main() {
	f := Foo{"bar"}
	fmt.Println(f)
}

根据Go语言规范,你观察到的行为确实是语言设计的一部分,但需要仔细区分类型转换和赋值兼容性。

在Go中,常量(包括无类型常量)具有特殊的类型处理规则。当使用无类型常量(如字符串字面量"abc")时,编译器允许在满足特定条件时进行隐式转换。

你的示例中:

f("abc") // 正常工作

这里的"abc"是一个无类型字符串常量,编译器允许它隐式转换为Foo类型,因为Foo的底层类型是string

而:

var s = "abc"
f(s) // 类型错误

这里的s已经是显式的string类型变量,需要显式类型转换。

这种行为在规范中有明确说明。根据Go规范关于常量的部分:

常量可以隐式转换为非常量类型,只要该值可以表示为该类型的值。

对于有类型变量,则需要显式转换:

type Foo string

func main() {
    var s string = "abc"
    var foo Foo
    
    // 需要显式转换
    foo = Foo(s)
    
    // 但字面常量可以隐式转换
    foo = "def" // 允许
    
    // 函数调用同理
    f := func(t Foo) {}
    f("ghi") // 允许 - 无类型常量
    f(Foo(s)) // 需要显式转换
}

这种设计允许在保持类型安全的同时,提供一定的灵活性处理常量值。如果你需要严格的类型检查,可以考虑使用结构体包装:

type Foo struct {
    value string
}

func NewFoo(s string) Foo {
    return Foo{value: s}
}

// 这样就不会接受字符串字面量了
func main() {
    f := func(t Foo) {}
    f(NewFoo("abc")) // 必须通过构造函数
    // f("abc") // 编译错误
}

所以这不是bug,而是Go语言类型系统中针对常量的特殊处理规则。

回到顶部