1

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?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
David M
  • 433
  • 1
  • 4
  • 10
  • 1
    If you want to do this by returning a slice, then the answer is no. You can, though, return an iterator that will allow read-only access to the slice members. – Burak Serdar Nov 20 '22 at 23:29
  • @BurakSerdar What I learned from another stackoverflow question https://stackoverflow.com/questions/14000534/what-is-most-idiomatic-way-to-create-an-iterator-in-go is that there is no obvious idiomatic way for iterators either, and even if there is one, it seems way too much an overkill for this, as I don't need concurrency and so on, just a read-only access. – David M Nov 20 '22 at 23:40
  • That is correct. But as I said, if you want to provide this as a slice without copying, you don't have much chance. Either don't worry about read-only access, copy it, or use iterator. – Burak Serdar Nov 20 '22 at 23:45

1 Answers1

3

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
        }
    }
}
blackgreen
  • 34,072
  • 23
  • 111
  • 129