0

I've had difficulty learning the basics of reflect, pointers and interface in go, so here's another entry level question I can't seem to figure out.

This code does what I want it to do - I'm using reflect to add another record to a slice that's typed as an interface.

package main

import (
  "reflect"
  "log"
)
type Person struct {
  Name string
}
func Add(slice interface{}) {
  s := reflect.ValueOf(slice).Elem()
  // in my actual code, p is declared via the use of reflect.New([Type])
  p := Person{Name:"Sam"}

  s.Set(reflect.Append(s,reflect.ValueOf(p)))
}

func main() {
  p := []Person{}
  Add(&p)
  log.Println(p)
}

If I changed the Add and main function to this, things don't work the way I want it to.

func Add(slice interface{}) {
  s := reflect.ValueOf(&slice).Elem()
  p := Person{Name:"Sam"}

  s.Set(reflect.Append(reflect.ValueOf(slice),reflect.ValueOf(p)))
  log.Println(s)
}

func main() {
  p := []Person{}
  Add(p)
  log.Println(p)
}

That is, the log.Println(p) at the end doesn't show a slice with the record Sam in it like the way I had hoped. So my question is whether it's possible for me to have Add() receive a slice that is not a pointer, and for me to still write some code in Add() that will produce the outcome shown in my first scenario?

A lot of my recent questions dance around this kind of subject, so it's still taking me a while to figure out how to use the reflect package effectively.

John
  • 32,403
  • 80
  • 251
  • 422

1 Answers1

2

No, it's not possible to append to a slice in a function without passing in a pointer to the slice. This isn't related to reflection, but to how variables are passed in to functions. Here's the same code, modified to not use reflection:

package main

import (
        "log"
)

type Person struct {
        Name string
}

func AddWithPtr(slicep interface{}) {
        sp := slicep.(*[]Person)

        // This modifies p1 itself, since *sp IS p1
        *sp = append(*sp, Person{"Sam"})
}

func Add(slice interface{}) {
        // s is now a copy of p2
        s := slice.([]Person)

        sp := &s

        // This modifies a copy of p2 (i.e. s), not p2 itself
        *sp = append(*sp, Person{"Sam"})
}

func main() {
        p1 := []Person{}
        // This passes a reference to p1
        AddWithPtr(&p1)
        log.Println("Add with pointer:   ", p1)

        p2 := []Person{}
        // This passes a copy of p2
        Add(p2)
        log.Println("Add without pointer:", p2)
}

(Above, when it says 'copy' of the slice, it doesn't mean the copy of the underlying data - just the slice)

When you pass in a slice, the function effectively gets a new slice that refers to the same data as the original. Appending to the slice in the function increases the length of the new slice, but doesn't change the length of the original slice that was passed in. That's why the original slice remains unchanged.

svsd
  • 1,831
  • 9
  • 14
  • Thanks, I have a question about this line `s := slice.([]Person)`. I understand that in this specific situation, copying is the undesired consequence of type assertion of a `slice`. But isn't there a way to get a reference to the underlying data of the `slice` without this undesired consequence? I thoughts reflect might have a solution here? – John Sep 01 '18 at 13:31
  • @John The expression copies the slice header, not the backing array. – Charlie Tumahai Sep 01 '18 at 13:35
  • 1
    Ok I guess I'll have to revisit tutorials on slices and keep in mind that slice is like a pointer to an array. And I have to be mindful of when I am passing pointer to a pointer to an array vs a pointer to an array. And when I'm copying a pointer to a pointer to an array vs. when I'm copying a pointer to an array – John Sep 01 '18 at 14:37
  • 1
    And that the append function makes a copy of the underlying array. And if I don't get my pointers straight via use of slices,I will lose track of which array in trying to make copies of. – John Sep 01 '18 at 14:40
  • @John yeah i do find https://blog.golang.org/go-slices-usage-and-internals and https://blog.golang.org/laws-of-reflection to be useful. Note that append() doesn't always make a copy of the underlying array. It makes a copy if the underlying array is full. One more interesting thing - try this out and see if you can explain what's happening: a := [3]int{1, 2, 3}; b := a[0:1]; c := a[0:2]; b = append(b, 9); fmt.Println(a, b, c) – svsd Sep 01 '18 at 14:51
  • @svsd the experiment with a,b,c you outlined for me very clearly demonstrates a few concepts for me. When I did the b=append(b,9), it checks first if b has sufficient capacity to assign 9 to position 1 in the underlying array. Because there is sufficient capacity, there is no need to make a copy of the underlying array. So a,b,c all show that 9 was written to the underlying array. – John Sep 01 '18 at 20:04