Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid memory address or nil pointer dereference when appending to slice of structs

Tags:

slice

struct

go

package main

import (
    "fmt"
)

type Person struct {
    name    string
}

func main() {
    p := make([]*Person, 0)
    p = append(p, &Person{"Brian"})
    fmt.Println(p[0].name)
    p = append(p, &Person{"Le Tu"})
    fmt.Println(p[1].name)
}

The above works fine.

package main

import (
    "fmt"
)

type Person struct {
    name    string
}

func main() {
    p := make([]*Person, 1) //Changed to 1 instead of 0
    p = append(p, &Person{"Brian"})
    fmt.Println(p[0].name)
    p = append(p, &Person{"Le Tu"})
    fmt.Println(p[1].name)
}

The above panics.

My understanding of append was that it hid the mechanics of extending/adding. Clearly, my mental model of using append as a sort of "push" for slices is incorrect. Can anyone explain to me why the second sample above panics? Why can't I just append my struct?

like image 772
Brian Graham Avatar asked Jan 31 '14 05:01

Brian Graham


2 Answers

For example,

package main

import (
    "fmt"
)

type Person struct {
    name string
}

func main() {
    p := make([]*Person, 1) //Changed to 1 instead of 0
    fmt.Println(len(p), p)
    p = append(p, &Person{"Brian"})
    fmt.Println(len(p), p)
    fmt.Println(p[1].name)
    fmt.Println(p[0])
    fmt.Println(p[0].name)
}

Output:

1 [<nil>]
2 [<nil> 0x10500190]
Brian
<nil>
panic: runtime error: invalid memory address or nil pointer dereference

p has length 1 before the append, length 2 after. Therefore, p[0] has the uninitialized pointer value nil and p[0].name is invalid.

The Go Programming Language Specification

Appending to and copying slices

The variadic function append appends zero or more values x to s of type S, which must be a slice type, and returns the resulting slice, also of type S.

Pointer types

The value of an uninitialized pointer is nil.

Selectors

The following rules apply to selectors:

4) If x is of pointer type and has the value nil and x.f denotes a struct field, assigning to or evaluating x.f causes a run-time panic.

like image 168
peterSO Avatar answered Sep 30 '22 06:09

peterSO


Reference page for make

When using make to build a slice, the first integer argument is the actual length of the created slice :

p := make([]*Person, 1)
// is equivalent to
p := []*Person{nil}  //<- slice of length 1, cells contain the default
                     //   zero value for the *Person type

If you want to create a slice of length 0, but with a predefined capacity, you need to use the 3 arguments version of make :

p := make([]*Person, 0, 100) //<- slice of length 0, but the first 100 append 
                             //   won't reallocate the slice

A simple example on how to use both cases :

//100-cell slice :
p1 := make([]*Person, 100)
for i := 0; i < 100; i++ {
    name := fmt.Sprintf("Foo %d", i+1)
    //you can access and assign to p1[i] because p1 has 100 cells :
    p1[i] = &Person{name}
}
fmt.Printf("p1[0].Name : %s\n", p1[0].Name)
fmt.Printf("p1[99].Name : %s\n", p1[99].Name)

//0-cell, 100-capacity slice :
p2 := make([]*Person, 0, 100)
for i := 0; i < 100; i++ {
    name := fmt.Sprintf("Foo %d", i+1)
    //you cannot access p2[i], the cell doesn't exist yet :
    p2 = append(p2, &Person{name})
}
fmt.Printf("p2[0].Name : %s\n", p2[0].Name)
fmt.Printf("p2[99].Name : %s\n", p2[99].Name)

play.golang link

like image 40
LeGEC Avatar answered Sep 30 '22 04:09

LeGEC