Duck typing in Go

2019-06-17 07:07发布

问题:

I want to write a Join function that takes arbitrary objects with a String() method:

package main

import (
    "fmt"
    "strings"
)

type myint int

func (i myint) String() string {
    return fmt.Sprintf("%d", i)
}

func main() {
    parts := []myint{myint(1), myint(5), myint(6)}
    fmt.Println(Join(parts, ", "))
}

func Join(parts []fmt.Stringer, sep string) string {
    stringParts := make([]string, len(parts))
    for i, part := range(parts) {
        stringParts [i] = part.String()
    }
    return strings.Join(stringParts , sep)
}

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

[]myint cannot be passed to Join, because it is not of type fmt.Stringer, so I have to write:

parts := []fmt.Stringer{myint(1), myint(5), myint(6)}

But what if I need parts for another operation where the values have to be ints? Should I cast a slice of myint to a slice of Stringer then?

回答1:

Q: Should I cast a slice of myint to a slice of Stringer then?

There are no casts in Go - only conversions. But conversion from a slice of concrete, non interface type to a slice of interface type is not possible, except by doing it explicitly in a loop. The reason is that, as for example in your case, the []myint backing array has a different memory layout than []fmt.Stringer has. (Related FAQ)

On a side note, you don't have to write:

parts := []myint{myint(1), myint(5), myint(6)}

This should work identically:

parts := []myint{1, 5, 6}


回答2:

Change the Join function to

func Join(sep string, parts ...fmt.Stringer) string {
}

makes it a lot easier:

package main

import (
    "fmt"
    "strings"
)

type myint int

func (i myint) String() string {
    return fmt.Sprintf("%d", i)
}

type myfloat float32

func (f myfloat) String() string {
    return fmt.Sprintf("%0.2f", f)
}

func main() {
    fmt.Println(Join(", ", myint(3), myfloat(3.5543)))
}

func Join(sep string, parts ...fmt.Stringer) string {
    stringParts := make([]string, len(parts))
    for i, part := range parts {
        stringParts[i] = part.String()
    }
    return strings.Join(stringParts, sep)
}