Go – How to remove an item from a slice by calling a method on the slice

goslice

Go has stumped me again. Hopefully someone can help. I've created a slice (mySlice) that contains pointers to structs (myStruct).

The problem is the "Remove" method. When we're inside "Remove" everything is fine, but once we return, the slice size hasn't changed, and so we see the last element listed twice.

I originally tried writing "Remove" using the same pattern used in the "Add" method, but it wouldn't compile and has been commented out.

I can get it to work by returning the newly created slice to the calling function, but I don't want to do this because mySlice (ms) is a singleton.

And if I hadn't asked enough already…

The code for the "Add" method is working, although I'm not sure how. From what I can gather "Add" is receiving a pointer to the slice header (the 3 item "struct"). From what I've read, the length and capacity of an slice don't get passed to methods (when passing by value), so perhaps passing a pointer to the slice allows the method to see and use the length and capacity thereby allowing us to "append". If this is true, then why doesn't the same pattern work in "Remove"?

Thanks very much for everyone's insights and help!

package main

import (
    "fmt"
)

type myStruct struct {
    a int
}
type mySlice []*myStruct

func (slc *mySlice) Add(str *myStruct) {
    *slc = append(*slc, str)
}

//does not compile with reason: cannot slice slc (type *mySlice)
//func (slc *mySlice) Remove1(item int) {
//  *slc = append(*slc[:item], *slc[item+1:]...)
//}

func (slc mySlice) Remove(item int) {
    slc = append(slc[:item], slc[item+1:]...)
    fmt.Printf("Inside Remove = %s\n", slc)
}

func main() {
    ms := make(mySlice, 0)
    ms.Add(&myStruct{0})
    ms.Add(&myStruct{1})
    ms.Add(&myStruct{2})
    fmt.Printf("Before Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
    ms.Remove(1) //remove element 1 (which also has a value of 1)
    fmt.Printf("After Remove:  Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
}

and the results…

Before Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{1}) %!s(*main.myStruct=&{2})]

Inside Remove = [%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2})]

After Remove:  Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2}) %!s(*main.myStruct=&{2})]

Best Answer

You were right the first time with Remove1(). Remove gets a copy of the slice and therefore cannot change the length of the slice.

The issue in your remove function is that according to order of operations in Go, slicing comes before dereferencing.

The fix is to change *slc = append(*slc[:item], *slc[item+1:]...) to *slc = append((*slc)[:item], (*slc)[item+1:]...).

However I would recommend the following for readability and maintainability:

func (slc *mySlice) Remove1(item int) {
    s := *slc
    s = append(s[:item], s[item+1:]...)
    *slc = s
}