I’m going to add to Holloway’s answer with some info that I hope will be useful:
There are two “classifications”* of types in Go: concrete and interface types.
Concrete types are:
- All of Go’s built in types like
bool
,int
,string
, etc. - All
struct
s - All arrays
- All pointers
- All slices
- All maps
- All functions
That is, concrete types are everything. Everything in Go that has a type is of a concrete type. If you want to convert from one concrete type to another, you have to use a conversion.
Interface types are descriptions of required methods that must exist on a concrete type in order to implement the interface. For example:
io.Reader
is an interface type and therefore consists of just a method set:
type Reader interface {
Read(b []byte) (n int, err error)
}
That is to say that any concrete type that has a Read
method with the same signature (one []byte
slice parameter and returns (int, error)
), then that concrete type implements io.Reader
. io.ReadCloser
is also an interface type:
type ReadCloser interface {
Read(b []byte) (n int, err error)
Close() error
}
Because the io.ReadCloser
interface defines the same Read
method as io.Reader
, then if your type currently implements io.Reader
and you add a Close
method that returns an error
now your type also implements io.ReadCloser
.
Putting it together a bit more:
The Body
field of the http.Response
type is of type io.ReadCloser
. We know now that io.ReadCloser
is just a method set. You never actually have a value of type io.ReadCloser
, the value has to have a real, concrete type, and that type must have Read
and Close
methods just like the io.ReadCloser
interface requires.
If you have a value (of some concrete type, which we don’t actually care about) that implements io.ReadCloser
and you want to use it as an io.Reader
, no conversion is necessary because you’re going from an interface type that contains a Read
method to another interface type that contains the same Read
method.
Consider the following example:
package main
import (
"fmt"
"io/ioutil"
)
type myType struct{}
func (myType) Read(a []byte) (b int, err error) {
return 0, fmt.Errorf("TODO!")
}
type myReader interface {
Read(b []byte) (n int, err error)
}
func main() {
var x myReader = myType{}
bs, err := ioutil.ReadAll(x)
fmt.Println("bs:", bs, "err:", err)
}
I made my own myReader
type for no reason here other than to demonstrate that interface types just define method sets and if the compiler knows at compile time that my x
variable is of some type that implements the myReader
method set, then the compiler knows that ioutil.ReadAll
requires a value that implements the io.Reader
method set which is the same set as myReader
, so it just works.
An *os.File
doesn’t have a Body
field because an *os.File
itself already has the Read
and Close
methods on it.
* Classification is a word I chose here but isn’t an official term for this divide, so I suspect you won’t see it called this way anywhere else.