How to expose a slice member with immutability?

Issue

I have a struct with a slice member, and a method to expose this slice. But I don’t want the caller being able to change the content of the slice. If I do this:

type A struct {
    slice []int
}

func (a *A) list() []int {
    return a.slice
}

it is not safe, as the content can be easily modified:

a := A{[]int{1, 2, 3}}
_ = append(a.list()[:2], 4)
fmt.Println(a.list()) // [1 2 4]

Obviously I can let list() return a copy of the slice to avoid this:

func (a *A) list() []int {
    return append([]int{}, a.slice...)
}

but that means every time when I just want to iterate through the slice I created a copy, which seems wasteful. Is there a way to do this without unnecessary copying?

Solution

As soon as you provide this slice to an external caller by returning it, it can be modified. If copying isn’t acceptable for performance reasons, you can implement a visitor:

func (a *A) Visit(f func(int)) {
    for _, v := range a.slice {
        f(v)
    }
}

This doesn’t expose the slice at all, and allows client code to see all items in the slice once. If the items aren’t pointers, or other mutable types, this is effectively read-only as the visitor callback will receive a copy of the value.

Optionally, the visitor could return a boolean in case you want to stop the iteration early.

func (a *A) Visit(f func(int) bool) {
    for _, v := range a.slice {
        if !f(v) {
            return
        }
    }
}

Answered By – blackgreen

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.