Why can I type alias functions and use them without casting?
Asked Answered
C

4

126

In Go, if you define a new type e.g.:

type MyInt int

You can't then pass a MyInt to a function expecting an int, or vice versa:

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

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

Fine. But why is it then that the same does not apply to functions? e.g.:

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
}

Now, I'm not complaining because it saves me having to explicitly cast newfunc to type MyFunc, as I would have to do in the first example; it just seems inconsistent. I'm sure there is a good reason for it; can anyone enlighten me?

The reason I ask is mainly because I would like to shorten some of my rather long function types in this way, but I want to make sure it's expected and acceptable to do this :)

Chaulmoogra answered 12/10, 2013 at 13:7 Comment(4)
type is rather more useful in Go than Scala. Scala only has type aliases, alas.Flunkey
Go now actually has type aliases github.com/golang/go/issues/18130Bryan
could someone explain the second code snippet ? I really cant get those function declarationsHamrah
Golang is terrible. They said no complex implicit conversion, but wrote complex assignment rules.Sunburst
C
180

Turns out, this is a misunderstanding that I had about how Go dealt with types, which can be resolved by reading the relevant part of the spec:

http://golang.org/ref/spec#Type_identity

The relevant distinction that I was unaware of was that of named and unnamed types.

Named types are types with a name, such as int, int64, float, string, bool. In addition, any type you create using 'type' is a named type.

Unnamed types are those such as []string, map[string]string, [4]int. They have no name, simply a description corresponding to how they are to be structured.

If you compare two named types, the names must match in order for them to be interchangeable. If you compare a named and an unnamed type, then as long as the underlying representation matches, you're good to go!

e.g. given the following types:

type MyInt int
type MyMap map[int]int
type MySlice []int
type MyFunc func(int)

the following is invalid:

var i int = 2
var i2 MyInt = 4
i = i2 //both named (int and MyInt) and names don't match, so invalid

the following is fine:

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)

I'm a bit gutted I didn't know that sooner, so I hope that clarifies the type lark a little for someone else! And means much less casting than I at first thought :)

Chaulmoogra answered 12/10, 2013 at 13:51 Comment(3)
You can also use is := make(MySlice, 0); m := make(MyMap), which is more readable in some contexts.Sigurd
the rule is defined here: go.dev/ref/spec#Assignability, which relies on the identity of two types.Trebizond
I was hoping to get more information on "why" this distinction with named and unnamed types is made. It does not seem obvious to me why one is assignable and the other is not. What types of mistakes could one make in the "int" case, requiring an explicit cast, which one could not make in the "func" case?Frutescent
H
20

Both the question and answer are pretty enlightening. However, I'd like to bring up a distinction which is not clear in lytnus's answer.

  • Named Type is different from Unnamed Type.

  • Variable of Named Type is assignable to variable of Unnamed Type, vice versa.

  • Variable of different Named Type is not assignable to each other.

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
}
Heir answered 16/8, 2014 at 18:14 Comment(0)
S
1

If the ultimate(as type of a type maybe a case)underlying type is a primitive type GO will not allow direct assignment of variable of one type to the another type directly and vice versa. However the value can be casted to the destination type and used.

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)

}
Shrivel answered 10/5, 2022 at 13:38 Comment(0)
C
0

Like the other people said anInt := 0 chooses a type for 0 rather than just a number. The only way I know to keep it without a type is to use const. If you use const instead of := then this can work.

func main() {
    const anInt = 0
    test(anInt)
}
Chirr answered 20/9, 2022 at 16:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.