Hacker News new | past | comments | ask | show | jobs | submit login

Yes, that's not good, but it seems like in practice this doesn't come up too often in Go?

More typically you're building a new slice containing the results of a query or other computation, and returning it without keeping a reference. Or instead of exposing an internal map, you have a Get method.




I have found this to be true of everything other than byte slices, where the result is some of the worst bugs I've had the displeasure of tracking down.

Many Go libraries like to offer passing a byte slice to reuse as a destination. Many Go libraries take byte slices or structs containing them as arguments. A common result is "loop over some input, read it into the reusable slice, pass the slice to the next step". It even sort of works - until the next step does something asynchronous.

It doesn't help that:

- Lots of things return a new slice "sometimes" - _this_ append() result is safe to pass on; this other one is not; this other one "it depends".

- The `x[:]` idiom to turn an array into a slice looks exactly like a Python copy, but actually shares the same backing store.

In the end we basically banned reusing slices as a result. (The rule I tell new programmers is "if you pass the slice elsewhere in a loop, the slice needs to be allocated within the loop.") Which is a shame, because the performance gain from safe reuse is often significant. But until the type system blocks us from changes, usually in the oblivious callee, which turn a safe use into an unsafe one, it's simply too much effort to remember and review each case.


In Python's Numpy library, `x[:]` also shares the same backing store. This bit me in the ass when I was learning Numpy.


Ouch. I guess this would be a reason to use strings more, since they're immutable.


Someone using a string when they really want a byte array/slice would fail review on its face. Excepting external APIs forcing "strings" on us, we only use the Go string type for UTF-8 code unit sequences (and this is a common assumption in the Go world).


Using string doesn't allow reuse, so you are leaving significant performance on the table.


This issue comes up with a reused bytes.Buffer.

    var buf bytes.Buffer
    buf.WriteString("foo")
    b := buf.Bytes()
    fmt.Printf("b == %v\n", string(b)) // b == foo
    buf.Reset()
    buf.WriteString("bar")
    fmt.Printf("b == %v\n", string(b)) // b == bar
Yes the documentation for .Bytes() says that "The slice is valid for use only until the next buffer modification" but people don't always read the docs for every method they use, especially if it's a method they've used a bunch before. Having a reusable buffer that you return the .Bytes() value from is very tempting. Bug-free code would either copy the result or not reuse the buffer.


> but it seems like in practice this doesn't come up too often in Go?

It comes up in any language that allows shared mutable state, so not much in functional languages which discourage mutability or Rust which discourages shared mutability.

On the other hand, most code tries to avoid shared mutable state by convention, there’s a nice bit in the Go lang design article that basically says convention is good enough, significantly simplifies the language, at the cost of the odd bug.


It apparently comes up often enough to be in Uber Go Style Guide.


I think the author are just being thorough




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: