Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

But the other side of this is that there's a contract violation going on: []byte can be mutated, but io.Reader cannot.

When I pass io.Reader I don't expect anything underneath it to be changed except the position. When I pass []byte it might be mutated.

So really solving the requires a whole new type - []constbyte or something (in general Go really needs stronger immutability guarantees - I've taken to putting Copy methods on all my struts just so I can get guaranteed independent copies, but I have to do it manually).



there is also the specific vs general trade-off: the general (io.Reader) being more flexible, while providing fewer opportunities for optimization. vice versa with the specific—be it []byte, or even []constbyte. i think it is just an inherent struggle with all abstractions.


> But the other side of this is that there's a contract violation going on: []byte can be mutated, but io.Reader cannot.

> When I pass io.Reader I don't expect anything underneath it to be changed except the position. When I pass []byte it might be mutated.

Where is it written in the contract that the underlying data source of io.Reader is immutable? I do not believe that is true. Even when io.Reader is backed by os.File, the OS may allow the file to be modified while you are reading it, possibly by the same process, using the io.Writer interface.

So while it's true that you certainly wouldn't expect an io.Reader itself to be mutable, an object that implements io.Reader certainly can expose interfaces that allow the underlying data source to be mutated. After all, bytes.Buffer is explicitly one such io.Reader; it has a Bytes() method:

> "The slice aliases the buffer content at least until the next buffer modification, so immediate changes to the slice will affect the result of future reads."

It's documented and totally allowed. Doesn't mean you should do it, but if an io.Reader that's backed by a buffer wants to expose its underlying buffer it isn't an issue of breaking contracts at the very least.

Another alternative to exposing an interface that returns []byte is getting clever with interfaces. Go sometimes will try to turn an io.Reader into an io.WriterTo: this can also avoid unnecessary copies.

> So really solving the requires a whole new type - []constbyte or something (in general Go really needs stronger immutability guarantees - I've taken to putting Copy methods on all my struts just so I can get guaranteed independent copies, but I have to do it manually).

A concept of constness would be nice, but it's trickier than it seems. An immutable type like string is fairly straight-forward. Immutable constant values of primitive types, also straightforward. Of course, Go has both. Constness as in const references to mutable values is weirder, because "const" sounds like it means something is immutable, since it does mean that in many contexts, but actually what it really means is that you can't mutate it, it still might get mutated behind your back, depending on if the actual value is const or not. I think that's at least a little unfortunate.

What I think I want is multiple concepts:

1. Variables that can't be re-assigned by default, akin to JavaScript `const` declarations.

2. Immutable const values of composite types, expanding the const primitives Go has today.

3. References that are immutable by default, mirroring Rust. Ideally you could take an immutable reference of constants that are typed, including composite const values, pushing them into rodata like you'd expect. To me this is better than C/C++'s concept of constness because it feels more intuitive when mutability is the exception; Having references marked const explicitly makes it look too similar to defining constants, making it easy to confuse immutability of the values to simply having an immutable reference to a mutable value. Having a `mut` case instead makes it clearer.

Can this be done in a theoretical Go-like programming language? Maybe... Will any of it ever be done? Probably not. The most feasible is probably 2., but even that would be pretty awkward due to the lack of immutable references; you'd have to always take a copy to actually use the value.


If the underlying data source changes then that is something happening on my side of the contract.

But io.Reader explicitly deals with populating a caller supplied buffer with whatever the next valid set of bytes is.

The caller thus knows the buffer won't change while the handle it.


Not 100% sure what is meant here. io.Reader itself only exposes the ability to do read-only access, but if you upcast you are not limited to only io.Reader's functionality. Now of course if you had a function that upcasted e.g. io.Reader to io.Writer and wrote to the provided stream, that would be weird. On the other hand, there is no such issue with merely grabbing an interface { Bytes() []byte } out of an io.Reader, calling the Bytes() method, and then not modifying the returned buffer. I don't think that introduces any hazards or contract violations and as best as I can tell is idiomatic Go and not discouraged.




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

Search: