Howabout an example that turned out to be a lot longer than I wanted it to be, but I think it’s hopefully still useful:
var i int
This declares a variable with the name i
whose type is int
. Declaring a string variable is similar:
var s string
The syntax is basically var name type
. Let’s now define a type:
type MyInt int
This declares a new type with the name MyInt
whose “underlying type” is a Go builtin int
. “Underlying” here isn’t like a base class. It means that a MyInt
is a new type, but it works the same as an int
. You can do arithmetic, logical operations, bit shifts, etc., but the compiler now treats it as a different type from int
.
Let’s look at a struct definition:
type MyStruct struct {
S string
I int
}
At first glance, this seems to work differently than MyInt
above, but it actually doesn’t:
| `type` keyword | type name | underlying type |
|----------------+-----------+----------------------------|
| type | MyInt | int |
| type | MyStruct | struct { S string; I int } |
It’s still just type name underlying_type
like type MyInt int
, but instead of the underlying type being a simple int
, it’s now a struct { S string; I int }
.
Just like you can do this with type definitions, you can do it with variables. Go has some neat features for handling constants that makes what I’m doing below unidiomatic, but if we get rid of the special constant handling, this is (for our purposes) more or less what the compiler sees:
var i int = int (5)
var s string = string ("Hello, world")
var x struct { X int; Y int} = struct { X int; Y int} {5, 6}
You can also declare and assign the variables with :=
like this (again pretending Go doesn’t have its special constant handling):
i := int(0)
s := string("Hello, World")
x := struct { X int; Y int} {5, 6}
Of course repeating the explicit struct type over and over would be horrible, which is why we usually define the struct into a type and use the type name like this:
x := MyStruct{5, 6}
The relationship between names and definitions of interface types works the same as struct types.
// MyStruct is a struct with a string field called S
// and then an int field called I.
type MyStruct struct {
S string
I int
}
// x is a variable with the same underlying type as MyStruct
// but it's not a MyStruct because we didn't say
//
// var x MyStruct = ...
//
var x struct{ S string; I int} = struct{ S string; I int}{"Hi", 5}
// MyInterface is implemented by anything that has a String() function
// that returns a string.
type MyInterface interface {
String() string
}
// y is a variable with the same underlying type as MyInterface.
// Interfaces are satisfied implicitly by using values that have the
// right method set that the interface needs, so this works:
var y interface{ String() string } = new(bytes.Buffer)
A lot of people feel like interface{}
is a special type in Go, but really it’s the same thing as the y
variable’s type definition above, just that interface{}
doesn’t have any methods listed between its {
and }
, so it is unambiguously called the “empty interface.”
After all of that, let’s look back at (basically) your example:
var values []interface{} = []interface{}{
thumbnail,
video.SubscriptionName,
video.Added.Format(constDateLayout),
video.Title,
progress,
backgroundColor,
video.ID,
duration,
progressText,
foregroundColor,
}
This declares a variable with the name values
whose type is a slice of interface{}
s. It could also be a slice of interface{ String() string }
s. It could also be a slice of int
s.
It could also be – brace yourself! – a slice of struct { S string; I int }
s:
var values []struct{ S string; I int} = []struct{ S string; I int}{
{"A", 1},
{"B", 2},
{"C", 3},
}
There are only two situations where I use anonymous struct types:
- As the underlying type of a named struct type, i.e.:
type MyStruct struct {
A string
B string
}
- When the struct is empty:
type singleton struct{}
func (singleton) DoSomething() {
panic("Using this singleton so it can implement an interface")
}
It’s a lot of material, but I hope it makes when to use the braces clearer.