A collection of Go quizzes to test your understanding of slices, pointers, structs, and decimals. Try to answer before revealing each answer!
Slice #1
func main() {
var a []int
for i := 1; i <= 5; i++ {
a = append(a, i)
fmt.Printf("%p, %p, %d, %d\n", &a, &a[0], len(a), cap(a))
}
}
Question
Consider the result of line 5 for each iteration.
- Will the address of
abe identical? - Will the address of
a[0]be identical? - What is the expected
len(a)? - What is the expected
cap(a)?
Reference: https://go.dev/blog/slices-introAnswer
0xc000010018, 0xc000012028, 1, 1
0xc000010018, 0xc000012050, 2, 2
0xc000010018, 0xc00007a020, 3, 4
0xc000010018, 0xc00007a020, 4, 4
0xc000010018, 0xc00007c040, 5, 8
a (the slice header) is always the same โ the variable itself doesn't move.a[0] changes when the backing array is reallocated (capacity exceeded).cap doubles each time a reallocation happens: 1 โ 2 โ 4 โ 8.
Slice #2
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7}
b := make([]int, 5, 5)
for _, i := range a {
b = append(b, i)
}
fmt.Println(b, len(b), cap(b)) // line 7
fmt.Println(b[19]) // line 8
c := b[:20]
fmt.Println(c, len(c), cap(c)) // line 10
c[11] = 99
fmt.Println(b, c) // line 12
c[19] = 99
fmt.Println(b, c) // line 14
}
Question
Determine the expected output of lines 7, 8, 10, 12, 14.
Answer
// line 7: [0 0 0 0 0 1 2 3 4 5 6 7] 12 20
// line 8: panic!
// line 10: [0 0 0 0 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0] 20 20
// line 12: b=[0 0 0 0 0 1 2 3 4 5 6 99] c=[0 0 0 0 0 1 2 3 4 5 6 99 0 0 0 0 0 0 0 0]
// line 14: b=[0 0 0 0 0 1 2 3 4 5 6 99] c=[0 0 0 0 0 1 2 3 4 5 6 99 0 0 0 0 0 0 0 99]
b starts at length 5, then 7 elements appended โ length 12, capacity doubles: 5 โ 10 โ 20.b[19] panics โ index 19 exceeds len(b)-1 (11).c := b[:20] is valid because 20 โค cap(b). c shares the same backing array as b.c[11] also changes b[11] โ same backing array.c[19] does NOT show on b โ index 19 is beyond len(b).
Slice #3
Clinical test for re-slicing!
func main() {
x := []int{1, 2}
fmt.Println(x[2:])
x = append(x, 3)
fmt.Println(x[:4])
}
Question
Will it panic? If yes, which line? If not, what is the result?
No panic. Slice expressions satisfy Reference: https://go.dev/ref/spec#Slice_expressionsAnswer
[] (valid: 2 <= len(x))[1 2 3 0] (valid: 4 <= cap(x) after append)0 <= low <= high <= cap(arr), not just len.
Pointer #1
type A struct { Name string; Age int }
type B struct { Name *string; Age *int }
var a = []A{{"john", 18}, {"danny", 19}, {"carl", 20}}
func main() {
var b []B
for _, v := range a {
b = append(b, B{Name: &v.Name, Age: &v.Age})
}
for _, v := range b {
fmt.Println(*v.Name, *v.Age)
}
}
Question
What is the expected output of the second loop?
The loop variable Fix: reference the original slice element by index.Answer
carl 20
carl 20
carl 20
v is reused each iteration โ its address is identical every time. All entries in b point to the same address, which holds the last value.
for i := range a {
b = append(b, B{Name: &a[i].Name, Age: &a[i].Age})
}
Pointer #2
func main() {
var a *int
*a = 111
fmt.Println(*a)
}
Question
What is the expected output?
Panic โ Answer
a is nil. Dereferencing a nil pointer causes a runtime panic.
func main() {
var a *int
b := 1
a = &b
*a = 111
fmt.Println(*a) // 111
}
Struct #1 (and Pointer)
type counter struct{ I int }
func (d *counter) inc() { d.I++ }
func NewCounter(in int) *counter { return &counter{I: in} }
func A(d counter) { d.inc() }
func B(d *counter) { d.inc() }
func main() {
c := NewCounter(0)
fmt.Println("start", c.I)
A(*c); A(*c); A(*c)
fmt.Println("result from A is", c.I)
B(c); B(c); B(c)
fmt.Println("result from B is", c.I)
}
Question
What is the expected output?
Answer
start 0
result from A is 0
result from B is 3
A receives a copy โ mutations don't affect the original.
B receives a pointer โ mutations affect the original.
Struct #2 (and Interface)
type counter struct{ I int }
func (d counter) inc() { d.I++ }
func NewCounter(in int) *counter { return &counter{I: in} }
type ICounter interface{ inc() }
func dofunc(d ICounter) { d.inc() }
func main() {
c := NewCounter(0)
fmt.Println("start", c.I)
dofunc(*c); dofunc(*c)
fmt.Println("result from A is", c.I)
dofunc(c); dofunc(c)
fmt.Println("result from B is", c.I)
}
Question
Can we pass either struct or pointer-to-struct to dofunc? What is the output?
Yes, both compile โ but the result is always Fix: change Reference: https://stackoverflow.com/questions/44370277/type-is-pointer-to-interface-not-interface-confusionAnswer
0. inc is a value receiver, so every call gets a copy.
start 0
result from A is 0
result from B is 0
inc to a pointer receiver func (d *counter) inc() and pass only the pointer.
Decimal
func main() {
v1 := decimal.NewFromInt(100)
var v2 decimal.Decimal
if v2 != decimal.Zero { // line 4
fmt.Println(v1.Div(v2))
}
if v2.Cmp(decimal.Zero) != 0 { // line 8
fmt.Println(v1.Div(v2))
}
}
Question
Between line 4 and line 8, which properly prevents dividing by zero?
Line 8 โ using Answer
.Cmp().decimal.Decimal is a struct. Using != compares field-by-field and can give unexpected results. Always use .Cmp(decimal.Zero) != 0 to compare decimal values correctly.
Top comments (0)