0

In the below code the first printAll has a compile error ./main.go:10:7: cannot use info (type []fs.FileInfo) as type fileInfoList in argument to print. How come this is the case? Shouldn't the fileInfo interface be met since each fs.FileInfo type has a Name method?

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    info, _ := ioutil.ReadDir("./")

    // info is of type []fs.FileInfo

    /*
        type FileInfo interface {
            Name() string
            // ...
        }
    */

    printAll(info) // DOESN'T WORK

    var list []fileInfo
    for _, f := range info {
        list = append(list, f)
    }
    printAll(list) // works

    d1 := defaultFileInfo{}
    d2 := defaultFileInfo{}
    dList := []fileInfo{&d1, &d2}
    printAll(dList) // works
}

type fileInfo interface {
    Name() string
}

func printAll(fileInfoList []fileInfo) {
    for _, f := range fileInfoList {
        fmt.Println(f.Name())
    }
}

type defaultFileInfo struct{}

func (d *defaultFileInfo) Name() string {
    return "..."
}

Talha
  • 807
  • 9
  • 13

1 Answers1

2

You're correct, fs.FileInfo implement the interface fileInfo!

However, that does not mean that you can assign a value of type []fs.FileInfo to a variable typed []fileInfo - these are completely different types.

In fact - the types cannot even be converted to each other, they are laid out completely differently in memory: interface values are a pair of {concrete type,data struct pointer}, and struct values are just what you see in the struct!

So, the short answer is that you have to do something like your loop which assigns values and appends them to the slice of interface values... behind the scenes what is happening is Go is creating an interface value for each of the struct slice elements for you, automatically.

A succinct way to say this all is: "Go interfaces types are covariant with the struct types that implement them, but slices of interface values are not type-covariant with slices of structs that implement those values."

For more info on slices of structs vs. interface types, see https://www.timr.co/go-interfaces-the-tricky-parts/

BadZen
  • 4,083
  • 2
  • 25
  • 48
  • 2
    I think the confusion here is that people see a slice as simply a decorator of sorts on a type. That a `foo` and a `[]foo` are still fundamentally the same type, the second one just has multiple of them. This is not the case in Go. Slices (and maps, channels, etc) are _first class types_. A `[]foo`'s type is _a slice_ that happens to be _composed_ of `foo`. Thus `[]foo` and `[]bar` are two different composite types, even if `foo` implements `bar`, because the _slice_ is the top-level type, not `foo` or `bar`. – Kaedys Jun 12 '21 at 06:30
  • 1
    Exactly. It again helps to thing of memory layout - a slice is really like a pointer to a struct that contains a pointer to a (fixed-size) array, a length, and an offset. – BadZen Jun 12 '21 at 15:37
  • 1
    That was super helpful @BadZen - Thanks! – Talha Jun 12 '21 at 19:26
  • @Kaedys, you are exactly right. Coming from a TypeScript and Java background I had the wrong assumption about slices in Go. – Talha Jun 12 '21 at 19:26
  • 1
    @Talha so you don't get tripped by this later (it's a common tripping hazard), the same principle applies to pointers as well. A `foo` and a `*foo` are distinct types. And most importantly, _they have distinct method sets_. The Go compiler does a bit of magic behind the scenes to allow, for example, a non-pointer method to be called on a pointer and vice versa (in most cases), but the difference is important when it comes to interfaces. Notably, pointer methods cannot be used to satisfy an interface if the type is passed in to the interface directly, rather than a pointer to the type. – Kaedys Jun 12 '21 at 19:43
  • 1
    You can find more details and some of the technical "why?" in this article (written by Rob Pike, one of the original designers of Go): https://blog.golang.org/laws-of-reflection – Kaedys Jun 12 '21 at 19:44