Home > Enterprise >  Unmarshalling in-place into a slice type in Go
Unmarshalling in-place into a slice type in Go

Time:01-16

Often when using go, not sure why, I get the urge to write something like

type data []event

especially when I know I'm going to be passing the slice around without thinking too much about its contents for much of the program. Sooner or later it's going to be time to unpack some data into that slice of events and I end up writing something like:

func (d *data)Unmarshal(b []byte){
  //... lots of sad code that never works
}

No matter what I do I can never quite figure out how to bless my slice type with an unmarshal method that turns some bytes into the data type in-place.

When I give up, I either write a simpler function like func UnmarshalData(b []byte) data which feels like a retreat and makes it hard to write interfaces, or change the type in the first place and make a struct like

type data struct {
  actuallyTheData []event
}

which feels like boilerplate purely to compensate for my lack of understanding.

So my question is: is it possible to write a function with a pointer receiver where the receiver is a slice type and that allows me to e.g. Unmarshal in-place?

The closest I can get, though it still doesn't work (and, let's face it, is pretty ugly), is something like:

type foo []int

func (f *foo) Unmarshal(s string) {
    numbers := strings.Split(s, ",")
    integers := make([]int, len(numbers))
    for i, n := range numbers {
        integer, err := strconv.Atoi(n)
        if err != nil {
            log.Fatal(err)
        }
        integers[i] = integer
    }
    my_f := foo(integers)
    f = &my_f
}

Here's the full example: https://go.dev/play/p/3q7qehoW9tm. Why doesn't it work? What am I misunderstanding?

CodePudding user response:

The last line in your Unmarshal function is overwriting the receiver itself, i.e. its address:

f = &my_f // changing the value of the pointer

The updated value won't be propagated to callers. From Declarations and Scope:

The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.

You must mutate the value that is being pointed to, then callers will see it upon dereference. (As a matter of fact, you don't have to convert to the defined slice type)

func (f *foo) Unmarshal(s string) {
    // ...
    integers := make([]int, len(numbers))
    *f = integers
}

Fixed playground: https://go.dev/play/p/3JayxQMClt-

  •  Tags:  
  • Related