Nohac
Nohac

Reputation: 61

Go type switch for type aliases

The answer to this is probably pretty obvious, but I'm asking anyways because I failed to find a good explanation for this.

I have two examples that I've made, they do pretty much the same thing, however, the first one uses int's and the other one is using interfaces:

Interface: https://play.golang.org/p/yb2oVaOJGF

type Apple interface{}
type Orange interface{}

type Basket struct {
    Fruit interface{}
}

func getFruites(basket Basket) {
    switch t := basket.Fruit.(type) {
    case Apple:
        apples, ok := t.(int)
        if !ok {
            log.Panic("Rotten apples!")
        }
        fmt.Println("Apples:", apples)
    case Orange:
        oranges, ok := t.(int)
        if !ok {
            log.Panic("Rotten oranges!")
        }
        fmt.Println("Oranges:", oranges)
    }
}

func main() {
    appleBasket := Basket{
        Fruit: Apple(10),
    }

    getFruites(appleBasket)

    orangeBasket := Basket{
        Fruit: Orange(10),
    }

    getFruites(orangeBasket)
}

Int: https://play.golang.org/p/_z8Mm0II41

type Apple int
type Orange int

type Basket struct {
    Fruit interface{}
}

func getFruites(basket Basket) {
    switch t := basket.Fruit.(type) {
    case Apple:
        apples := t
        fmt.Println("Apples:", apples)
    case Orange:
        oranges := t
        fmt.Println("Oranges:", oranges)
    }
}

func main() {
    appleBasket := Basket{
        Fruit: Apple(10),
    }

    getFruites(appleBasket)

    orangeBasket := Basket{
        Fruit: Orange(10),
    }

    getFruites(orangeBasket)
}

Could someone please explain why they produce different output?

Upvotes: 2

Views: 2933

Answers (3)

Tarun Khandelwal
Tarun Khandelwal

Reputation: 491

Since you said other answers didn't feel intuitive, I am attempting to explain it from a completely different standpoint. The case you mention has nothing to do with your type aliases and everything to do with how interfaces work in Go. To more clearly show the difference between the two codes you provided, here is a different code that gives the similar output as your interface example, but doesn't use type aliases at all.

Playground link: https://play.golang.org/p/0tyDV28cp2b

package main

import (
    "fmt"
    "log"
)

// Create two interfaces that have exactly the same signature
// Why? So every struct that implements one automatically implements the other

type interface1 interface {
    Foo() int
}

type interface2 interface {
    Foo() int
}

// Create an impl for the interfaces defined above
type impl int

func (i impl) Foo() int { return int(i) }

// Note that the Fruit is of type "interface2"
// Despite that, it will always go to "interface1" case in the type switch
// It is because anything that implements interface2
// also implements interface1
type Basket struct {
    Fruit interface2
}

func getFruites(basket Basket) {
    switch t := basket.Fruit.(type) {
    case interface1:
        ans, ok := t.(impl)
        if !ok {
            log.Panic("Rotten ans!")
        }
        fmt.Println("Interface1:", ans)
    case interface2:
        ans, ok := t.(impl)
        if !ok {
            log.Panic("Rotten ans!")
        }
        fmt.Println("Interface2:", ans)
    }
}

func main() {
    appleBasket := Basket{
        Fruit: impl(10),
    }

    getFruites(appleBasket)

    orangeBasket := Basket{
        Fruit: impl(10),
    }

    getFruites(orangeBasket)
}

Upvotes: 0

JT.
JT.

Reputation: 482

Because you are defining the Apple and Orange types to an empty interface they are satisfied by anything.

The empty interface is in essence nothing specific.

Everything in the first example can be asserted as the int type neither Apple or Oranges.

Look at this small change to your first example's code.

package main

import (
    "fmt"
    "log"
)

type Apple interface{}
type Orange interface{}

type Basket struct {
    Fruit interface{}
}

func getFruites(basket Basket) {
    switch t := basket.Fruit.(type) {
    case int: 
        fmt.Println("empty interfaces are satisfying an int")
    case Apple:
        apples, ok := t.(int)
        if !ok {
            log.Panic("Rotten apples!")
        }
        fmt.Println("Apples:", apples)
    case Orange:
        oranges, ok := t.(int)
        if !ok {
            log.Panic("Rotten oranges!")
        }
        fmt.Println("Oranges:", oranges)
    }
}

func main() {
    appleBasket := Basket{
        Fruit: Apple(10),
    }

    getFruites(appleBasket)

    orangeBasket := Basket{
        Fruit: Orange(10),
    }

    getFruites(orangeBasket)
}

https://play.golang.org/p/LDtvbXnjT7

Upvotes: 1

captncraig
captncraig

Reputation: 23098

In the case where Apple and Orange are defined as interface{}, the type switch is satisfied by anything that implements that interface.

For the empty interface, that is anything at all, and it takes the first case that matches.

When Apple and Orange are "aliases" for a non-interface type (int), only a variable that is explicitly an Apple or Orange can satisfy the type switch.

Upvotes: 1

Related Questions