0

I have a type that implements the stringer interface

// RowID stores the ID of a single row in a table
type RowID []string

// String implements Stringer interface for RowID
func (r RowID) String() string {
    return fmt.Sprintf("[%s]", strings.Join(r, ", "))
}

And I have a function that I want to pass a slice of this type (or any other type that implements the Stringer interface) to.

// PrintChanges ...
func PrintChanges(ids []fmt.Stringer) {
    for _, id := range ids {
        fmt.Println(id)
    }
}

However, The go compiler gives me an error:

cannot use rowIDs (type []RowID) as type []fmt.Stringer in argument to PrintChanges

I can pass a RowID to a func that accepts a single fmt.Stringer

func PrintChange(id fmt.Stringer) {
    fmt.Println(id)
}

...
    PrintChange(RowID{"1", "24"})

But for some reason I am not able to pass a slice of RowID to a func that accepts a slice of fmt.Stringer. What am I missing?

Go Playground

Joy Peterson
  • 148
  • 2
  • 11

1 Answers1

1

Keep it simple

It is considered okay by professional Go programmers to repeat functions like this for every type, or to have a for loop over every slice you want to print. This is because Go aims to be as easy to read as possible, i.e. a person who reads a chunk of code for the first time should not be asking questions like "which function overload will this function call go to" (common pitfall in C++, Go does not have function overloads). So you can just write in main():

Playground: https://ideone.com/IL3rGR

    for _, id := range rowIDs { fmt.Println(id) }

Simple and concise.

Note that fmt.Println(id) does not call your String() function

This is because the fmt library uses the reflect library and hardcodes behavior for the string type, which you are trying to replace. RowID instances are also string instances, the library always prefers string over its type aliases. I would say it is a bug in the library:

Library source: https://golang.org/src/fmt/print.go#L649

    // Some types can be done without reflection.
    switch f := arg.(type) {
...
    case string:
        p.fmtString(f, verb)

If you really want to

You can use a function that takes an interface{} and makes a runtime reflect type cast to a slice of Stringers. Note that this means you will not see type mismatches during compilation, only in runtime:

Playground: https://ideone.com/vlrBP9

func castToStringerSlice(iface interface{}) ([]fmt.Stringer, bool /* ok */) {
    if reflect.TypeOf(iface).Kind() != reflect.Slice {
        return nil, false
    }

    v := reflect.ValueOf(iface)
    stringers := make([]fmt.Stringer, v.Len())

    for i := 0; i < v.Len(); i++ {
        stringers[i] = v.Index(i)
    }

    return stringers, true
}

func PrintChanges(iface_ids interface{}) {
    ids, ok := castToStringerSlice(iface_ids)
    if !ok {
        log.Fatal(errors.New("the argument to PrintChanges must be a slice of Stringers"))
    }
    for _, id := range ids {
        fmt.Println(id)
    }
}

Resources:

Miłosz Łakomy
  • 996
  • 5
  • 12