为什么我可以输入别名函数并在不进行转换的情况下使用它们?

125
在Go语言中,如果您定义一个新类型,例如:
type MyInt int

您不能将MyInt传递给期望int的函数,反之亦然:

func test(i MyInt) {
    //do something with i
}

func main() {
    anInt := 0
    test(anInt) //doesn't work, int is not of type MyInt
}

好的。但是为什么同样的规则不适用于函数呢?例如:
type MyFunc func(i int)
func (m MyFunc) Run(i int) {
    m(i)
}

func run(f MyFunc, i int) {
    f.Run(i)
}

func main() {
    var newfunc func(int) //explicit declaration
    newfunc = func(i int) {
        fmt.Println(i)
    }
    run(newfunc, 10) //works just fine, even though types seem to differ
}

现在,我并不是在抱怨,因为这样可以避免我必须在第一个例子中将newfunc显式转换为MyFunc类型;只是它看起来不一致。我相信这样做有很好的理由;有人能启发我吗?

我问的原因主要是因为我想以这种方式缩短一些非常长的函数类型,但我想确保这样做是预期且可接受的:)


在Go中,type比Scala更有用。不幸的是,Scala只有类型别名。 - Rick-777
4
现在,Go 语言实际上已经具有类型别名功能。https://github.com/golang/go/issues/18130 - Hut8
有人能解释一下第二段代码吗?我真的无法理解那些函数声明。 - DevX
Golang很糟糕。他们说不会有复杂的隐式转换,但却写了复杂的赋值规则。 - Ireina
4个回答

179
原来,我对Go如何处理类型存在误解,可以通过阅读规范的相关部分来解决:http://golang.org/ref/spec#Type_identity
我不知道的相关区别是命名类型和未命名类型。 命名类型是有名称的类型,例如int、int64、float、string、bool。此外,使用'type'创建的任何类型都是命名类型。 未命名类型是诸如[]string、map[string]string、[4]int之类的类型。它们没有名称,只有对应于它们如何被构造的描述。
如果您比较两个命名类型,则名称必须匹配才能互换。如果您比较一个命名类型和一个未命名类型,则只要底层表示匹配,就可以了!
例如,给定以下类型:
type MyInt int
type MyMap map[int]int
type MySlice []int
type MyFunc func(int)

以下内容是无效的:
var i int = 2
var i2 MyInt = 4
i = i2 //both named (int and MyInt) and names don't match, so invalid

以下是可以的:
is := make([]int)
m := make(map[int]int)
f := func(i int){}

//OK: comparing named and unnamed type, and underlying representation
//is the same:
func doSlice(input MySlice){...}
doSlice(is)

func doMap(input MyMap){...}
doMap(m)

func doFunc(input MyFunc){...}
doFunc(f)

我有点失望,我希望早点知道这个,所以我希望这能为其他人澄清一下类型的问题!这意味着比我最初想象的要少得多的投射 :)


2
你也可以使用 is := make(MySlice, 0); m := make(MyMap),在某些情况下更易读。 - R2B2
1
规则在此处定义:https://go.dev/ref/spec#Assignability,它依赖于两种类型的身份。 - Izana
1
规则在这里定义:https://go.dev/ref/spec#Assignability,它依赖于两种类型的身份。 - undefined
我希望能够获得关于为什么会有命名和未命名类型之间的区别的更多信息。对我来说,为什么一个可以赋值而另一个却不能并不明显。在"int"的情况下,可能会出现哪些需要显式转换的错误,而在"func"的情况下则不会出现这样的错误? - Oliver

20

这个问题和答案都很有启发性。不过,我想提出一个区别,在lytnus的回答中并不清楚。

  • 命名类型 不同于 未命名类型

  • 变量 命名类型 可以分配给变量 未命名类型 ,反之亦然。

  • 不同 命名类型 的变量不能彼此分配。

http://play.golang.org/p/uaYHEnofT9

import (
    "fmt"
    "reflect"
)

type T1 []string
type T2 []string

func main() {
    foo0 := []string{}
    foo1 := T1{}
    foo2 := T2{}
    fmt.Println(reflect.TypeOf(foo0))
    fmt.Println(reflect.TypeOf(foo1))
    fmt.Println(reflect.TypeOf(foo2))

    // Output:
    // []string
    // main.T1
    // main.T2

    // foo0 can be assigned to foo1, vice versa
    foo1 = foo0
    foo0 = foo1

    // foo2 cannot be assigned to foo1
    // prog.go:28: cannot use foo2 (type T2) as type T1 in assignment
    // foo1 = foo2
}

1
如果最终(作为类型的一种类型可能是一种情况)的基础类型是原始类型,GO将不允许直接将一个类型的变量直接赋值给另一个类型,并反之亦然。但是,该值可以转换为目标类型并使用。
package main
import (
    "fmt"
)

type T int

type U T

type V U

type UU U

func main() {

    var s int 
    var t T   
    var u U
    var uu UU

    s = 9
    t = 10
    u = 11
    uu = 111

    fmt.Println(s)
    fmt.Println(t)
    fmt.Println(u)
    fmt.Println(uu)

    fmt.Println("========")

    //s = t
    //u = s
    //v = s

    u = (U)(uu)

    //fmt.Println(s)
    //fmt.Println(u)
    fmt.Println(u)

}

0
就像其他人所说的那样,anInt := 0会为0选择一种类型,而不仅仅是一个数字。我知道保持它没有类型的唯一方法是使用const。如果您使用const而不是:=,那么这将起作用。
func main() {
    const anInt = 0
    test(anInt)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接