当一个类型是本机类型时,为什么golang禁止对同一基础类型赋值?

当一个类型是本机类型时,为什么golang禁止对同一基础类型赋值?,go,Go,考虑以下代码: package main import "fmt" type specialString string func printString(s string) { fmt.Println(s) } // unlike, say, C++, this is not legal GO, because it redeclares printString //func printString(s specialString) { // fmt.Println("S

考虑以下代码:

package main
import "fmt"

type specialString string

func printString(s string) {
    fmt.Println(s)
}

// unlike, say, C++, this is not legal GO, because it redeclares printString
//func printString(s specialString) {    
//  fmt.Println("Special: " + s)
//}

func main() {
    ss := specialString("cheese")
    // ... so then why shouldn't this be allowed?
    printString(ss)
}
我的问题是:为什么语言被定义为不允许在
main()
中调用
printString(ss)
?(我不是在寻找关于Golang赋值规则的答案;我已经读过它们,而且我看到,String和String都具有相同的‘基础类型’,并且这两种类型都是‘命名的’——如果您考虑要命名的泛型类型‘字符串’,而Golang显然是这样做的,那么它们在规则下是不可指派的)) 但是为什么规则是这样的呢?通过将内置类型视为“命名”类型,并阻止您将命名类型传递给所有接受相同底层内置类型的标准库函数,可以解决什么问题?有人知道这里的语言设计者的想法吗


在我看来,它似乎在代码中创建了许多无意义的类型转换,并且不鼓励在实际有意义的地方使用强类型。

这是因为Go没有类继承。它使用结构组合。命名类型不会从其基础类型继承属性(这就是为什么它不被称为“基类型”)

因此,当您使用预定义类型
string
的基础类型声明命名类型
specialString
时,您的新类型与基础类型是完全不同的类型。这是因为Go假设您希望为新类型分配不同的行为,并且在运行时之前不会检查其基础类型。这就是为什么Go是一种静态和动态的语言

打印时

fmt.Println(reflect.TypeOf(ss))        // specialString
您得到的是
specialString
,而不是
string
。如果查看
Println()
,定义如下:

func Println(a ...interface{}) (n int, err error) {
        return Fprintln(os.Stdout, a...)
}
这意味着您可以打印任何预先声明的类型(int、float64、string),因为所有这些类型都实现了至少零个方法,这使得它们已经符合空接口并作为“可打印”传递,而不是您的命名类型
specialString
,在编译时仍然未知。我们可以对照
specialString
打印
接口{}
的类型进行检查

type specialString string 
type anything interface{}

s := string("cheese")
ss := specialString("special cheese")
at := anything("any cheese")

fmt.Println(reflect.TypeOf(ss))     // specialString
fmt.Println(reflect.TypeOf(s))      // string
fmt.Println(reflect.TypeOf(at))     // Wow, this is also string!
你可以看到,
specialString
对它的身份一直很淘气。现在,看看它在运行时传递到函数时是如何工作的

func printAnything(i interface{}) {
        fmt.Println(i)
}

fmt.Println(ss.(interface{}))       // Compile error! ss isn't interface{} but
printAnything(ss)                   // prints "special cheese" alright
ss
已成为函数的
接口{}
。到那时,Go已经制作了
ss
一个
接口{}


如果你真的想从深层次理解,这真的是无价之宝。

我相信最初作者的逻辑是,命名类型的命名是有原因的——它代表不同的东西,而不仅仅是底层类型

我想我在《golang nuts》的某个地方读过,但记不起确切的讨论

考虑以下示例:

type Email string
您将其命名为Email,因为您需要表示电子邮件实体,“string”只是它的简化表示,从一开始就足够了。但稍后,您可能希望将电子邮件更改为更复杂的内容,例如:

type Email struct {
    Address string
    Name    string
    Surname string
}

这将破坏所有隐式处理电子邮件的代码,假定它是字符串。

仅出于安全原因。强而严格的输入会减少出错的可能性。您使用“specialstring”的示例意味着它是一个字符串,您可能会得到Go没有的子类型的印象。您引入了一个
类型foobar string
来拥有一个foobar(实际上是一个字符串),但是为什么用printstring函数打印一个foobar是有意义的呢?foobar不是
字符串
,而是foobar。如果你只需要一个普通的字符串,那么就不需要引入一个“特殊字符串”,只需要使用普通字符串。这里还有一些FAQ中的更多的推理:也可以考虑库的部分的设计,比如“代码>时间。持续时间< /代码>。该语言防止普通
int64
s被视为
time.Duration
,而不考虑显式单位。你批评的功能可以(而且是!)用来编码我们从中学就知道的“相同单位”规则。如果使用得当,它应该会更难犯同样的错误,因为公制转换错误导致卫星坠落。我发现这个答案信息丰富,所以我投了赞成票,但最终我选择了divan的答案,因为他的答案更直接地针对我问题的关键:“这解决了什么问题?”——我想问的是“为什么语言设计者认为这种行为是个好主意”,而不是“语言实现的哪些方面导致它以这种方式工作?”?“--不过,我感谢你花时间如此详细地解释,我完全不同意。转到允许为
键入电子邮件字符串编制索引<代码>电子邮件[:]
在第一种情况下有效,但在第二种情况下肯定会导致编译错误。事实证明,这不会让开发人员免于自取灭亡。我也对这个问题提出了质疑,并认为这是早期golang开发者的一个固执己见的决定。我需要接受它。