Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upproposal: Go 1/2 - Immutable Types #27975
Comments
gopherbot
added this to the Proposal milestone
Oct 2, 2018
gopherbot
added
the
Proposal
label
Oct 2, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
dsnet
Oct 2, 2018
Member
Nice document; clearly you spent a while on it. I only briefly glanced over it.
Copies are the only way to achieve immutability in Go 1.x, but copies inevitably degrade runtime performance. This dilemma encourages Go 1.x developers to either write unsafe mutable APIs when targeting optimal runtime performance or safe but slow and copy-code bloated ones.
Not exactly the only way. An alternative approach is to have an opaque type with only exported methods that provide read-only access, which is how reflect.Type achieves immutability. The v2 protobuf reflection API also takes this approach. It has other downsides (like needing to manually create methods for each read-only operation), but pointing out that there are non-copy approaches to immutability.
|
Nice document; clearly you spent a while on it. I only briefly glanced over it.
Not exactly the only way. An alternative approach is to have an opaque type with only exported methods that provide read-only access, which is how |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
networkimprov
commented
Oct 2, 2018
•
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
kirillDanshin
Oct 2, 2018
@networkimprov please no $ in names, I don't think we need to start another PHP again
kirillDanshin
commented
Oct 2, 2018
|
@networkimprov please no |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 2, 2018
Contributor
As far as I can tell in my initial skim, this doesn't solve the memchr problem. In C the standard library memchr function, which returns a pointer to the first occurence of a character in a string, is defined as char *memchar(const char *, char). The problem is that in terms of types, if memchr is passed a const char * it should return a const char *, and if memchr is passed a char * it should return a char *. That is, it should preserve the const-ness of its first argument. But there is no way to write that in C. And I don't see how to write that in your proposal.
Other comments:
- Is there any difference between this proposal and the use of
constas a type qualifier in C, other than the logical extension to interfaces? - Look up "const-poisoning."
- Pedantically, I don't particularly like using the word "immutable" to describe the parameter to
func F(const []int). That slice is not immutable; all that declaration says is thatFwill not change it. This is particularly clear if you writefunc F(s1 const []int, s2 []int)and then call it asF(s, s). You can't say thats1is immutable withinF, because ifs2changes thens1will change. - Do we really have to worry about immutable containers of mutable values? Yes, that comes up once in a while, but it is often enough to make it worth writing
const [] const T?
|
As far as I can tell in my initial skim, this doesn't solve the Other comments:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 3, 2018
@dsnet
getters/setters are doing just that: they copy stuff from the inner scope of the struct. You
don't want to copy everything every time, and you certainly don't want to do it manually. Writing setters, getters, cloners just for the sake of ensuring immutability is not only quite tedious but also very error-prone due to pointer aliasing, which is the scariest part actually. Copy-code tends to be rather complicated in Go, one wrong copy (like copying a pointer, or naively copying a reference type such as a slice) and you've introduced aliasing that could have terrific, non-obvious consequences. With immutable types though, having read-only aliasing is just fine because there's no mutable shared state.
Currently, the safest way of avoiding manual copying of large structs are interfaces. You could define 2 interfaces where one of them lacks the mutating methods and return interfaces from the getters only. This is an "okay" solution, but it doesn't solve internal mutability problems. In big open source projects many people are working on the code, intentions must be unambiguous, clear and precise, which they're currently not. Can you automatically ensure that the methods implementing the read-only interface do not mutate the object for sure, even after merging a pull request from an external developer who's not fully aware of your intentions? You can't! You'll have to write proper unit tests and carefully analyze each and every commit! With immutable types you declare your interface methods immutable and you can be 100% sure that any implementation of it trying to mutate the object will fail the compilation. Apart from that, interfaces aren't free, they do have a slight runtime cost due to indirection, so having an option to avoid them for performance reasons while still preserving safety is a good thing!
I always prefer to solve these kinds of problems declaratively. I declare what is mutable/immutable while the compiler does all the dirty work of making sure neither me, nor my coworkers, nor the open source contributors sending in their pull requests shoot themselves in the foot introducing bugs. Isn't this the way compiled languages should make our lives easier?
romshark
commented
Oct 3, 2018
•
|
@dsnet Currently, the safest way of avoiding manual copying of large structs are interfaces. You could define 2 interfaces where one of them lacks the mutating methods and return interfaces from the getters only. This is an "okay" solution, but it doesn't solve internal mutability problems. In big open source projects many people are working on the code, intentions must be unambiguous, clear and precise, which they're currently not. Can you automatically ensure that the methods implementing the read-only interface do not mutate the object for sure, even after merging a pull request from an external developer who's not fully aware of your intentions? You can't! You'll have to write proper unit tests and carefully analyze each and every commit! With immutable types you declare your interface methods immutable and you can be 100% sure that any implementation of it trying to mutate the object will fail the compilation. Apart from that, interfaces aren't free, they do have a slight runtime cost due to indirection, so having an option to avoid them for performance reasons while still preserving safety is a good thing! I always prefer to solve these kinds of problems declaratively. I declare what is mutable/immutable while the compiler does all the dirty work of making sure neither me, nor my coworkers, nor the open source contributors sending in their pull requests shoot themselves in the foot introducing bugs. Isn't this the way compiled languages should make our lives easier? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 3, 2018
@networkimprov
As section 3. of the design document clearly states: immutability by default and explicit mutability qualification through mut is preferable, but would only be possible in a backward-incompatible Go 2.x specification, which is not to be expected any time soon (AFAIK, the "Go 2" they're advertising is rather a Go 1.13+ because the folks at Google aren't big fans of breaking compatibility as it seems).
Naming conventions would break backward-compatibility. Old Go 1.x code could either stop compiling or fail at linting, which is unacceptable. This proposal aspires to preserve backward-compatibility at all cost. There's also a somewhat related question in the FAQ by the way.
romshark
commented
Oct 3, 2018
|
@networkimprov Naming conventions would break backward-compatibility. Old Go 1.x code could either stop compiling or fail at linting, which is unacceptable. This proposal aspires to preserve backward-compatibility at all cost. There's also a somewhat related question in the FAQ by the way. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
randall77
Oct 3, 2018
Contributor
You may want to read Russ' evaluation of a read-only slices proposal. It contains a lot of the issues that this proposal should grapple with.
|
You may want to read Russ' evaluation of a read-only slices proposal. It contains a lot of the issues that this proposal should grapple with. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
dsnet
Oct 3, 2018
Member
getters/setters are doing just that: they copy stuff from the inner scope of the struct.
But they don't have to. If the inner field is a composite type, the getter can return an opaque type that internally holds a pointer to the composite type and only provides exported read-only getter methods.
Writing setters, getters, cloners just for the sake of ensuring immutability is not only quite tedious but also very error-prone due to pointer aliasing, which is the scariest part actually
I've written several immutable APIs in this way. I absolutely agree that it is tedious, but I personally don't think it was "very error-prone" from the perspective of the API author. Pointer aliasing is not inherently the problem; it is problematic if a pointer to a non-opaque type leaks to the public API. However, I find it relatively straight-forward to review the public API and reason that it doesn't violate immutability.
Can you automatically ensure that the methods implementing the read-only interface do not mutate the object for sure, even after merging a pull request from an external developer who's not fully aware of your intentions? You can't!
Since read-only APIs are usually just getters, they are not terribly complicated such that you would accidentally mutate the object (e.g., it is not hard to review this and reason it is read-only).
In big open source projects many people are working on the code, intentions must be unambiguous, clear and precise, which they're currently not.
An opaque read-only API does make the intention clear. The lack of any setter methods is a clear signal that the user should not (and cannot) mutate anything.
Apart from that, interfaces aren't free, they do have a slight runtime cost due to indirection, so having an option to avoid them for performance reasons
Interfaces are one such implementation, but it doesn't have to be. It can be a concrete type too:
type MutableStruct struct {
Field int
...
}
type ImmutableStruct struct { p *MutableStruct }
func (p ImmutableStruct) GetField() int { return p.p.Field }There is practically no runtime cost to this as the compiler can inline all the getters as if they were nested field accesses (or slice indexes, map lookups, etc).
I am bringing the technique up not as an end-all alternative to your proposal, but more so to counter the claim that "copies are the only way to achieve immutability ... [which] degrades runtime performance". It is a legitimate approach taken today to address this problem, which the proposal seems to gloss over.
I agree that there are disadvantages to opaque APIs with read-only getters (especially with regard to their tediousness and perhaps the lack of implicit casting), but I think it would help the case of a proposal trying to add immutability to acknowledge techniques done today to work around the problem and show that the benefit of adding immutability outweighs the cost (e.g., complexity in type system and the "const poisoning" mentioned earlier).
But they don't have to. If the inner field is a composite type, the getter can return an opaque type that internally holds a pointer to the composite type and only provides exported read-only getter methods.
I've written several immutable APIs in this way. I absolutely agree that it is tedious, but I personally don't think it was "very error-prone" from the perspective of the API author. Pointer aliasing is not inherently the problem; it is problematic if a pointer to a non-opaque type leaks to the public API. However, I find it relatively straight-forward to review the public API and reason that it doesn't violate immutability.
Since read-only APIs are usually just getters, they are not terribly complicated such that you would accidentally mutate the object (e.g., it is not hard to review this and reason it is read-only).
An opaque read-only API does make the intention clear. The lack of any setter methods is a clear signal that the user should not (and cannot) mutate anything.
Interfaces are one such implementation, but it doesn't have to be. It can be a concrete type too: type MutableStruct struct {
Field int
...
}
type ImmutableStruct struct { p *MutableStruct }
func (p ImmutableStruct) GetField() int { return p.p.Field }There is practically no runtime cost to this as the compiler can inline all the getters as if they were nested field accesses (or slice indexes, map lookups, etc). I am bringing the technique up not as an end-all alternative to your proposal, but more so to counter the claim that "copies are the only way to achieve immutability ... [which] degrades runtime performance". It is a legitimate approach taken today to address this problem, which the proposal seems to gloss over. I agree that there are disadvantages to opaque APIs with read-only getters (especially with regard to their tediousness and perhaps the lack of implicit casting), but I think it would help the case of a proposal trying to add immutability to acknowledge techniques done today to work around the problem and show that the benefit of adding immutability outweighs the cost (e.g., complexity in type system and the "const poisoning" mentioned earlier). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
networkimprov
Oct 3, 2018
Naming conventions would break backward-compatibility.
Above I suggested a go-vet switch to support a naming convention. Such a convention would be optional, permanently. As would a mut keyword. Lots of folks don't want to code that way.
There has been plenty of discussion about const-ness over the years, yet the two priorities for Go2 are error handling & generics, and code for them presumably won't land for a couple years (there is no defined schedule as yet).
networkimprov
commented
Oct 3, 2018
•
Above I suggested a go-vet switch to support a naming convention. Such a convention would be optional, permanently. As would a There has been plenty of discussion about |
gopherbot
added
the
LanguageChange
label
Oct 3, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alvaroloes
Oct 3, 2018
I love how well done the proposal is. Thank you for your hard work on this.
I have just one thought that I would like to share:
It is stated in the proposal that we can't cast an immutable var to a mutable one, as we could break the immutability. The opposite, casting a mutable var to an immutable one, can be done, which makes sense. However, in this case, we can break immutability. I know that you talk about this in section 4.5. How are constants different from immutable types?, but this could lead to very subtle and unexpected situations.
For example, let's say we have this Slice type:
type Slice []int
func (s *Slice) Add(elem const int) {
*s = append(*s, elem)
}
func (s const Slice) ImmutableVersion() const [] const int {
return s
}And then we use it like this:
slice := Slice{1,2,3,4}
immutableVersion := slice.ImmutableVersion()
// Now immutableVersion = {1,2,3,4}
slice.Add(5)
// Now immutableVersion = {1,2,3,4,5} It has changedThis behavior could be unexpected and lead to confusion, as you were guaranteed by the type system that the var immutableVersion was immutable.
This can be even worse with slices as, if the capacity is exceeded, append will allocate a new underlying array, what means that the immutableVersion won't be changed. So we don't really know if/when the immutableVersion will change.
This won't happen if the method ImmutableVersion() returns a copy.
Don't get me wrong! I love the proposal. I think it is the best one I have seen for immutability and I would like it to come true as soon as possible.
I just wanted to know the general opinion about the case I have posted.
Thanks!
alvaroloes
commented
Oct 3, 2018
|
I love how well done the proposal is. Thank you for your hard work on this. I have just one thought that I would like to share: For example, let's say we have this Slice type: type Slice []int
func (s *Slice) Add(elem const int) {
*s = append(*s, elem)
}
func (s const Slice) ImmutableVersion() const [] const int {
return s
}And then we use it like this: slice := Slice{1,2,3,4}
immutableVersion := slice.ImmutableVersion()
// Now immutableVersion = {1,2,3,4}
slice.Add(5)
// Now immutableVersion = {1,2,3,4,5} It has changedThis behavior could be unexpected and lead to confusion, as you were guaranteed by the type system that the var This won't happen if the method Don't get me wrong! I love the proposal. I think it is the best one I have seen for immutability and I would like it to come true as soon as possible. I just wanted to know the general opinion about the case I have posted. Thanks! |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nemith
Oct 3, 2018
Contributor
In 2.6. Immutable Interface Methods I am not sure I understand why enforcement of mutability on the interface is important. This seems more like an implementation detail and could severely limit the usefulness of interfaces if abused too much. The answer in 4.7 doesn't make much sense to me.
|
In 2.6. Immutable Interface Methods I am not sure I understand why enforcement of mutability on the interface is important. This seems more like an implementation detail and could severely limit the usefulness of interfaces if abused too much. The answer in 4.7 doesn't make much sense to me. |
rsc
added
the
Go2
label
Oct 3, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 3, 2018
The difference between C-style const and the proposed const
Is there any difference between this proposal and the use of const as a type qualifier in C, other than the logical extension to interfaces?
There is! The const in C is just confusing while the const in this proposal always targets the type on the right:
| goal | Go | C |
|---|---|---|
| reassignable pointer to writable T | * T |
Т * |
| reassignable pointer to read-only T | * const T |
T const * |
| read-only pointer to writable T | const * T |
T * const |
| read-only pointer to read-only T | const * const T |
T const * const |
In fact, C-style const is so confusing that const char * is the exact same as char const *. You also can cast const to non-const in C, which you can't in this proposal. Please do not compare the const from C with the proposed const for Go, we don't wanna do it the horrible C-way, but rather learn from its mistakes!
Const-Poisoning
If by "const-poisoning" you mean the ability to cast immutable types to their mutable counterparts then I've got good news for you: C-style const poisoning is impossible with this proposal.
IV. Mutable types can be cast to their immutable counterparts, but not the other way around.
Terminology
Pedantically, I don't particularly like using the word "immutable"
This proposal is not about functional-programming-style "immutable objects", it's about "immutable types". Immutable objects remain immutable after they're initialized while immutable types are types you can't perform mutations on. "Types" and "objects" are obviously not the same and this proposal doesn't propose immutable objects.
Immutable Reference Types
Do we really have to worry about immutable containers of mutable values? Yes, that comes up once in a while, but it is often enough to make it worth writing
const [] const T?
Reference types such as pointers, slices and maps shall be no exception in the concept of immutable types (slices and maps are reference types. Yes, they're implemented by a struct but to us users they're opaque). Section 5.2.1. describes why transitive immutability is to be avoided. Basically, it makes the entire concept of immutable types useless when the developer faces a slightly more complex situation like when a reference, such as a pointer, must point to an exact mutable object. But it's the complex situations the developers need the compiler's help most! Transitive immutability will force the developer to throw immutable types out the window because they limit his/her expressiveness making it totally useless.
romshark
commented
Oct 3, 2018
•
The difference between C-style
|
| goal | Go | C |
|---|---|---|
| reassignable pointer to writable T | * T |
Т * |
| reassignable pointer to read-only T | * const T |
T const * |
| read-only pointer to writable T | const * T |
T * const |
| read-only pointer to read-only T | const * const T |
T const * const |
In fact, C-style const is so confusing that const char * is the exact same as char const *. You also can cast const to non-const in C, which you can't in this proposal. Please do not compare the const from C with the proposed const for Go, we don't wanna do it the horrible C-way, but rather learn from its mistakes!
Const-Poisoning
If by "const-poisoning" you mean the ability to cast immutable types to their mutable counterparts then I've got good news for you: C-style const poisoning is impossible with this proposal.
IV. Mutable types can be cast to their immutable counterparts, but not the other way around.
Terminology
Pedantically, I don't particularly like using the word "immutable"
This proposal is not about functional-programming-style "immutable objects", it's about "immutable types". Immutable objects remain immutable after they're initialized while immutable types are types you can't perform mutations on. "Types" and "objects" are obviously not the same and this proposal doesn't propose immutable objects.
Immutable Reference Types
Do we really have to worry about immutable containers of mutable values? Yes, that comes up once in a while, but it is often enough to make it worth writing
const [] const T?
Reference types such as pointers, slices and maps shall be no exception in the concept of immutable types (slices and maps are reference types. Yes, they're implemented by a struct but to us users they're opaque). Section 5.2.1. describes why transitive immutability is to be avoided. Basically, it makes the entire concept of immutable types useless when the developer faces a slightly more complex situation like when a reference, such as a pointer, must point to an exact mutable object. But it's the complex situations the developers need the compiler's help most! Transitive immutability will force the developer to throw immutable types out the window because they limit his/her expressiveness making it totally useless.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
networkimprov
Oct 3, 2018
@ianlancetaylor's 2010 blog post on const: https://www.airs.com/blog/archives/428
He describes const as compiler-enforced documentation, except for variable definition where it directs the compiler to use read-only memory. (And he discusses const poisoning.)
But there is another way that const can affect generated code; a const function argument can be passed by reference instead of by value, so that a compound object (Go struct or array) isn't copied onto the stack. (Go maps and slice contents are already passed by reference.)
I'd like to see Go support that kind of const.
networkimprov
commented
Oct 3, 2018
|
@ianlancetaylor's 2010 blog post on const: https://www.airs.com/blog/archives/428 He describes But there is another way that const can affect generated code; a const function argument can be passed by reference instead of by value, so that a compound object (Go struct or array) isn't copied onto the stack. (Go maps and slice contents are already passed by reference.) I'd like to see Go support that kind of const. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 3, 2018
Contributor
@romshark Const poisoning refers to what happens when you add const to one function, and then you have to add const to every function that it calls, and then you have to add const to every function that those functions call, etc.
Then sometimes you discover that you have to change some lower function to not use const, for perfectly valid reasons, and const poisoning refers to the problem of removing const from the entire call chain.
These aren't made up problems, they are real issues that arise in practice in large programs.
Also, let me ask the comparison with C again: is there any difference between the use of const in this proposal and the use of const in C, other than syntax?
|
@romshark Const poisoning refers to what happens when you add Then sometimes you discover that you have to change some lower function to not use These aren't made up problems, they are real issues that arise in practice in large programs. Also, let me ask the comparison with C again: is there any difference between the use of |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 3, 2018
@ianlancetaylor
This is the reason Section 2.12. even exists. It's definitely true that once you've got immutable types you need to use them everywhere. This, essentially, is the price you pay for having predictability, clarity, and safety. It's kind of a stopper for the Go 1.x proposal, I agree, but in Go 2.x this must be done with immutable types by default to avoid having ambiguity from the very start and not end up with the Go 1.x problem of having to fix all libraries including the standard one.
I honestly can't imagine what "perfectly valid reasons" you need to have to, for example, make any of the lower functions called by strings.Join() not use const for the slice of strings you pass to a because a should be guaranteed to not be touched in any way neither by strings.Join() nor by any of the functions up the stack. And since immut -> mut casting is inherently forbidden I see no problems here. Once you provide a contract (API) - you either support it or you break it, not silently violate it!
Can you give us an example of when we'd suddenly discover that we actually needed mutable inputs and thus have to "remove immutability"?
There is no semantic difference between the C-style const and the proposed const. The proposed const is a better execution of the C version, but in the end, they both serve a similar purpose:
- make the intentions of the code author clear and reliable.
- protect the code from undesired and unexpected mutations.
romshark
commented
Oct 3, 2018
•
|
@ianlancetaylor I honestly can't imagine what "perfectly valid reasons" you need to have to, for example, make any of the lower functions called by Can you give us an example of when we'd suddenly discover that we actually needed mutable inputs and thus have to "remove immutability"? There is no semantic difference between the C-style
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 3, 2018
Contributor
Problematic cases happen in large, complex, programs, so there are no small examples. In terms of your proposal, the kind of thing that happens is that you start passing a map around, and there is no reason to change it, so you mark it const everywhere. Let's say it's a map of names to codes or something. Then later you realize that the names sometimes change, but you can only discover that deep in the call hierarchy. So you have to add some code there to change the map, and you have to remove const from all the callers. Obviously that is easy to nit pick, it's just an example, and, yes, I've seen this kind of thing happen in real code. In fact in C++ this is where most people reach for const_cast.
That aside, I note that you haven't replied to my memchr comment. In that regard you might want to read through #22876, which tries to address that problem through "permission genericity."
|
Problematic cases happen in large, complex, programs, so there are no small examples. In terms of your proposal, the kind of thing that happens is that you start passing a map around, and there is no reason to change it, so you mark it That aside, I note that you haven't replied to my |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 4, 2018
@ianlancetaylor
Yes, that is a problem indeed and the "permission genericity" concept proposed by Jonathan Amsterdam in #22876 does look promising (I wonder how I missed that). I'll give it a thought, it probably makes sense to integrate the concept of immutability genericity into this proposal as well.
romshark
commented
Oct 4, 2018
|
@ianlancetaylor |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
beoran
Oct 4, 2018
I appreciate the thought that went into this proposal, however I think immutability is mostly an academic concern. Seeing how complex this proposal is, I'd like to hear of some experience reports where accidental mutation actually caused serious problems in a large Go code base. In my experience, accidental mutation is a relatively rare cause of bugs in programming. Therefore I think it does not warrant the troubles of having to use const constantly, or having to constantly worry about const correctness.
beoran
commented
Oct 4, 2018
|
I appreciate the thought that went into this proposal, however I think immutability is mostly an academic concern. Seeing how complex this proposal is, I'd like to hear of some experience reports where accidental mutation actually caused serious problems in a large Go code base. In my experience, accidental mutation is a relatively rare cause of bugs in programming. Therefore I think it does not warrant the troubles of having to use const constantly, or having to constantly worry about const correctness. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
imatmati
Oct 4, 2018
I appreciate the thought that went into this proposal, however I think immutability is mostly an academic concern. Seeing how complex this proposal is, I'd like to hear of some experience reports where accidental mutation actually caused serious problems in a large Go code base. In my experience, accidental mutation is a relatively rare cause of bugs in programming. Therefore I think it does not warrant the troubles of having to use const constantly, or having to constantly worry about const correctness.
I am more than reluctant to introduce more complex solution than the problem you're trying to solve. Go is a simple language to a certain extent, we don't need to copy other language just to make some swing.
imatmati
commented
Oct 4, 2018
I am more than reluctant to introduce more complex solution than the problem you're trying to solve. Go is a simple language to a certain extent, we don't need to copy other language just to make some swing. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 4, 2018
I haven't seen any comments from those who've given a
Personally I don't like the idea behind const types. It reminds me of C's const which, IMO, was a disaster that complicated C's otherwise simple type system.
I am aware that it brings in a lot of safety, but it comes at the cost of a lot of readability. I also understand that sometimes sacrifices to readability need to be made to increase safety, but I'm not sure if this is one of them.
deanveloper
commented
Oct 4, 2018
|
I haven't seen any comments from those who've given a Personally I don't like the idea behind I am aware that it brings in a lot of safety, but it comes at the cost of a lot of readability. I also understand that sometimes sacrifices to readability need to be made to increase safety, but I'm not sure if this is one of them. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bcmills
Oct 4, 2018
Member
In fact in C++ this is where most people reach for
const_cast.
In the C++ codebases I've maintained, the vast majority of uses of const_cast were in order to overload const and non-const member functions.
That technique is recommended in Effective C++ (and in this StackOverflow answer), but disrecommended in the isocpp.org core guidelines.
That seems to support the theory that const-parametricity is sufficient to avoid the need for such casts.
In the C++ codebases I've maintained, the vast majority of uses of That technique is recommended in Effective C++ (and in this StackOverflow answer), but disrecommended in the isocpp.org core guidelines. That seems to support the theory that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 4, 2018
Contributor
I didn't mean to imply it was the only time people used const_cast.
|
I didn't mean to imply it was the only time people used |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bcmills
Oct 4, 2018
Member
That does raise the problem of migration paths, though: there are widespread APIs today, such as io.Writer that accept read-write slices, and a naive approach to const in the type system would require at least one of:
- workarounds at every call site, such as conversions through
unsafe.Pointer; - escape hatches for calling existing (Go 1) APIs, such as (unsound) bivariant subtyping; or
- large-scale updates to ~all existing packages.
In contrast, a dynamic analysis (such as the one in #22048, possibly only enforced when the race detector is enabled) would only affect existing APIs that actually perform unexpected writes.
|
That does raise the problem of migration paths, though: there are widespread APIs today, such as
In contrast, a dynamic analysis (such as the one in #22048, possibly only enforced when the race detector is enabled) would only affect existing APIs that actually perform unexpected writes. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
LeonineKing1199
Oct 4, 2018
I'm not sure why you're bringing up io.Writer, @bcmills.
Mutable variables can safely be accepted as const and the API documentation for io.Writer itself attempts to convey its immutability requirements.
LeonineKing1199
commented
Oct 4, 2018
|
I'm not sure why you're bringing up Mutable variables can safely be accepted as |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jimmyfrasche
Oct 4, 2018
Member
Languages that get this right start with immutable by default and do a lot of work behind the scenes to make everything seamless and need some syntax sugar to make it easy to use. I love immutability, but I don't think it'd really fit in to Go well.
I do want to provide my position on one of @ianlancetaylor's points, though.
[T]he memchr problem. In C the standard library
memchrfunction, which returns a pointer to the first occurence of a character in a string, is defined aschar *memchar(const char *, char). The problem is that in terms of types, ifmemchris passed aconst char *it should return aconst char *, and ifmemchris passed achar *it should return achar *. That is, it should preserve the const-ness of its first argument.
Let's say A is const char * and B is char *.
memchr(A, B) is fine.
memchr(A, A) and memchr(B, B) should both be type errors. Each should need an explicit step to transition to/from an immutable copy: something like memchr(A, mutable_copy_of(A)) and memchr(immutable_copy_of(B), B).
This is the same as if this were a Go func with the signature func memchr(string, []byte) []byte where A is a string and B is a []byte. You can't just call memchr(A, A) and have it be the same as string(memchr(A, []byte(A))).
This inevitably leads to needing (at least) two versions of everything to avoid copying everything dozens of time or not being able to use (im)mutable versions of types because a needed dependency made the choice for you.
Sometimes generics could help with that by letting you write multiple versions simultaneously, but that means making a lot of things generic that otherwise would not need to be, essentially swapping "const poisoning" with "generics poisoning".
Having freeze/unfreeze/isFrozen sidesteps this but adds a dynamic axis to a static type system. Instead of having two versions of each function you have one version that needs to cope with both at runtime.
|
Languages that get this right start with immutable by default and do a lot of work behind the scenes to make everything seamless and need some syntax sugar to make it easy to use. I love immutability, but I don't think it'd really fit in to Go well. I do want to provide my position on one of @ianlancetaylor's points, though.
Let's say
This is the same as if this were a Go func with the signature This inevitably leads to needing (at least) two versions of everything to avoid copying everything dozens of time or not being able to use (im)mutable versions of types because a needed dependency made the choice for you. Sometimes generics could help with that by letting you write multiple versions simultaneously, but that means making a lot of things generic that otherwise would not need to be, essentially swapping "const poisoning" with "generics poisoning". Having |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Dragomir-Ivanov
Oct 4, 2018
@romshark I also had bad experience with C++ const poisoning, and it is not due to "bad design/API" but mere new features/requirements added, that need to modify something deep in the call chain, that nobody though should be modified before. And usually these features are needed for "yesterday", I did the only possible thing - const_cast right on the spot. So @ianlancetaylor concerns are very valid. One thought on all this const correctness thing: We have to declare that if something is marked as const in the current call chain, doesn't mean that it will not change by other part of the program running concurrently where they are not const types. It is just that current call chain can't change it ( unless you remove the const with something along the lines of const_cast ). Yeah, const correctness it is not a simple beast.
Dragomir-Ivanov
commented
Oct 4, 2018
•
|
@romshark I also had bad experience with C++ const poisoning, and it is not due to "bad design/API" but mere new features/requirements added, that need to modify something deep in the call chain, that nobody though should be modified before. And usually these features are needed for "yesterday", I did the only possible thing - |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 4, 2018
Contributor
@jimmyfrasche I don't quite grasp your comments on memchr. I probably didn't describe the issue properly. In Go terms, a similar problem arises with bytes.Split. Suppose we want to add a const qualifer to bytes.Split. We might write
func Split(s, sep [] const byte) [][]byteAfter all, Split does not modify the contents of s or sep, it just returns slices of s. But we can't write that function, because the slices of s will have type [] const byte. So instead we write
func Split(s, sep [] const byte) [][] const byteBut now we can't use the function if we plan to modify the resulting slices, because even though we pass in a normal []byte we get back [][] const byte when we really want [][]byte.
That is, the memchr problem is that we want the const qualifier on the result to be there or not depending on whether the qualifier is there or not on the first input argument. But the type system doesn't give us a way to write that.
|
@jimmyfrasche I don't quite grasp your comments on func Split(s, sep [] const byte) [][]byteAfter all, func Split(s, sep [] const byte) [][] const byteBut now we can't use the function if we plan to modify the resulting slices, because even though we pass in a normal That is, the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bcmills
Oct 4, 2018
Member
I'm not sure why you're bringing up io.Writer, @bcmills.
Mutable variables can safely be accepted as const and the API documentation for
io.Writeritself attempts to convey its immutability requirements.
io.Writer today accepts non-const slices.
If you attempt to make the argument to io.Writer a const []byte in the interface definition, you will break implementations of Write. that call non-const functions with that argument (perhaps because they are erroneous, or perhaps because those methods are actually const but not labeled as such yet).
On the other hand, if you do not make the argument to io.Writer const, you will break anyone attempting to call Write on a const []byte: even though the documentation says that the slice is logically read-only, the type system does not reflect that fact.
So what you're left with is one of four options:
- Update the entire world to use
constatomically. (This is essentially impossible.) - Try to build a directed acyclic call graph, and update from the leaves inward. (Likely also impossible.)
- Start with an unenforced
const, and try to update the world (without backsliding!) until you can turn on enforcement. (Technically plausible but unlikely to complete in finite time.) - Leave a ~permanent loophole (such as
const_cast, or a “Go 1” personality that ignoresconst) in the language, leave the existing Go 1 APIs as-is, and rely on the Go 2 migration process to apply the loophole as needed.
If you attempt to make the argument to On the other hand, if you do not make the argument to So what you're left with is one of four options:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jimmyfrasche
Oct 4, 2018
Member
@ianlancetaylor I get your point but I don't think that's a reasonable thing to expect to come baked into an immutability proposal.
What you're asking for is being able to say func Split(s, sep T) []T where T can be either []byte or []const byte. That's an issue for generics to solve not obscure rules about implicit casting and quantum function signatures.
|
@ianlancetaylor I get your point but I don't think that's a reasonable thing to expect to come baked into an immutability proposal. What you're asking for is being able to say |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
LeonineKing1199
Oct 4, 2018
If you attempt to make the argument to io.Writer a const []byte in the interface definition, you will break implementations of Write. that call non-const functions with that argument (perhaps because they are erroneous, or perhaps because those methods are actually const but not labeled as such yet).
Imo, this is a feature and not a bug. The API docs stress the importance of not mutating the input slice. If functions break this it means a couple of things. One, people will have to update their functions to use const slices which sucks but I think is acceptable. Two, it'll catch genuine breaks in the invariants laid out by io.Writer.
In terms of preserving backwards compatibility, this would be most easily accomplished in C++ via function overloading (i.e. const vs non-const overloads).
Since Go doesn't have that option and prides itself on being opinionated, I think its best bet is to not make io.Writer take a const slice but Go can still add const to the language so new libraries going forward can encode immutability at the type system level.
LeonineKing1199
commented
Oct 4, 2018
Imo, this is a feature and not a bug. The API docs stress the importance of not mutating the input slice. If functions break this it means a couple of things. One, people will have to update their functions to use const slices which sucks but I think is acceptable. Two, it'll catch genuine breaks in the invariants laid out by In terms of preserving backwards compatibility, this would be most easily accomplished in C++ via function overloading (i.e. Since Go doesn't have that option and prides itself on being opinionated, I think its best bet is to not make |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
networkimprov
Oct 4, 2018
@romshark it seems clear to me that Go won't admit a const qualifier, despite all the happy thumbs on your well-crafted document.
Can you come at this from a different angle? Maybe an optional mutability indicator that stops at the package boundary, so that a mutable-aware package can call APIs or be called by programs which lack mutability awareness.
It's not the wholistic system you wanted, but it might draw less anti-aircraft fire :-)
networkimprov
commented
Oct 4, 2018
|
@romshark it seems clear to me that Go won't admit a Can you come at this from a different angle? Maybe an optional mutability indicator that stops at the package boundary, so that a mutable-aware package can call APIs or be called by programs which lack mutability awareness. It's not the wholistic system you wanted, but it might draw less anti-aircraft fire :-) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 4, 2018
Contributor
@jimmyfrasche That is a reasonable point, but after all the issue of "the qualifier of the result type ought to be inherited from the qualifier of the input type" really does come up in fairly simple examples. It's a mark against this proposal that it can't handle that case. It would be nice to not have to reach for generics when there are only two possible type arguments. We know from long experience that there are problems with the const qualifier in C; we shouldn't repeat them merely because they are familiar.
|
@jimmyfrasche That is a reasonable point, but after all the issue of "the qualifier of the result type ought to be inherited from the qualifier of the input type" really does come up in fairly simple examples. It's a mark against this proposal that it can't handle that case. It would be nice to not have to reach for generics when there are only two possible type arguments. We know from long experience that there are problems with the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jimmyfrasche
Oct 4, 2018
Member
@ianlancetaylor The C/C++ approach is bad. It is not an example to follow. I wouldn't even consider what it does immutability, really. It gives you some minor guarantees but it's ultimately an exercise in const correctness for the sake of const correctness.
You don't really get immutability unless you force copying between values of T and immutable T and disallowing writes to values of the latter (unsafe shenanigans notwithstanding).
My point is that mutable T and immutable T are different types—equally as different as any two distinct types.
They have a lot of similarities, surely, but it's superficial. These types are also superficially similar:
type T struct { X, Y int }
type CT struct { X, Y int }
but a function that accepts and returns T wouldn't be expected to just work with CT. You need to introduce a type parameter if you want to write a signature like that. If for no reason other than to keep the relation between the types clear!
The functional languages generally get immutability right. If you want to explore immutability you should look at the various ML/Miranda descendants, not at C or C++. They make all of it work by creating value semantics with a lot of sleight of hand and many heap pointers and many more optimizations to make up for all the duplication and copying and pointer chasing, though. I'm not sure it would work well with Go. I suspect it could not be made to work with Go. That doesn't make me happy because I'd love to have immutable types.
|
@ianlancetaylor The C/C++ approach is bad. It is not an example to follow. I wouldn't even consider what it does immutability, really. It gives you some minor guarantees but it's ultimately an exercise in const correctness for the sake of const correctness. You don't really get immutability unless you force copying between values of My point is that mutable They have a lot of similarities, surely, but it's superficial. These types are also superficially similar:
but a function that accepts and returns The functional languages generally get immutability right. If you want to explore immutability you should look at the various ML/Miranda descendants, not at C or C++. They make all of it work by creating value semantics with a lot of sleight of hand and many heap pointers and many more optimizations to make up for all the duplication and copying and pointer chasing, though. I'm not sure it would work well with Go. I suspect it could not be made to work with Go. That doesn't make me happy because I'd love to have immutable types. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 5, 2018
Go is not a functional programming language and I really don't think we should introduce an update which will break every single Go program in any update...
If we should be learning (what to do AND what not to do) about immutability from any programming languages, we should be from programming languages that fill the same purpose that Go does, such as Rust, C-family, etc.
deanveloper
commented
Oct 5, 2018
|
Go is not a functional programming language and I really don't think we should introduce an update which will break every single Go program in any update... If we should be learning (what to do AND what not to do) about immutability from any programming languages, we should be from programming languages that fill the same purpose that Go does, such as Rust, C-family, etc. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 5, 2018
I'm currently trying to solve the qualifier-verbosity problem. Check out Go 2: mutability qualification propagation #20, this idea is to be discussed.
romshark
commented
Oct 5, 2018
|
I'm currently trying to solve the qualifier-verbosity problem. Check out Go 2: mutability qualification propagation #20, this idea is to be discussed. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
LeonineKing1199
Oct 5, 2018
Go is not a functional programming language and I really don't think we should introduce an update which will break every single Go program in any update...
You can add a language feature without mandating an update to existing APIs.
LeonineKing1199
commented
Oct 5, 2018
You can add a language feature without mandating an update to existing APIs. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 5, 2018
You can add a language feature without mandating an update to existing APIs.
But then we come into the adoption problem like before. We physically can't change the io.Writer signature without breaking every existing implementation of io.Writer. Especially if we "learn from " and adopt an "immutability by default" feature, which really would break every current Go program.
deanveloper
commented
Oct 5, 2018
But then we come into the adoption problem like before. We physically can't change the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 5, 2018
I would like to mention however that the memchr and bytes.Split problems can be easily resolved by returning indices rather than slices. This is a bit more verbose and doesn't really solve the problem but
deanveloper
commented
Oct 5, 2018
•
|
I would like to mention however that the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
LeonineKing1199
Oct 5, 2018
But then we come into the adoption problem like before.
This isn't a real problem. No one has to do anything. Just because Go has goto doesn't mean we're forced to use it.
If people don't want to adopt immutability, they don't have to.
LeonineKing1199
commented
Oct 5, 2018
This isn't a real problem. No one has to do anything. Just because Go has If people don't want to adopt immutability, they don't have to. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
beoran
Oct 5, 2018
True, but in this particular issue, the proposal is to make immutability the default in Go 2, which would mean everyone is forced to used it and everyone will have to rewrite their Go 1 programs...
beoran
commented
Oct 5, 2018
|
True, but in this particular issue, the proposal is to make immutability the default in Go 2, which would mean everyone is forced to used it and everyone will have to rewrite their Go 1 programs... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
networkimprov
Oct 6, 2018
Avoid both const and incompatibility of mut:
romshark/Go-1-2-Proposal---Immutability#23
networkimprov
commented
Oct 6, 2018
|
Avoid both |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mcspring
Oct 8, 2018
Contributor
I suggest we should introduce Annotation, knows as compile tags in go, for marking the immutable definations.
- for interface
// here we go
// +immutable
type IMutIfaces {
Func()
}
// now we got the immutability funcs embedded
type Ifaces {
IMutIfaces
AnotherFunc()
// Func() // This SHOULD NOT compile, since we embedded an immutable Func early.
}- for func
type My struct {
name string
}
// here we go
// +immutable
func (my *My) Name() string {
return my.name
}
type You struct {
My
name string
}
func (you *You) YourName() string {
return you.name
}
// This SHOULD NOT compile, too.
//func (you *You) Name() string {
// return you.name
//}- for field
We DO NOT need this. You should use un-exported principle if you do not want any one changing it somewhere.
|
I suggest we should introduce Annotation, knows as compile tags in go, for marking the immutable definations.
// here we go
// +immutable
type IMutIfaces {
Func()
}
// now we got the immutability funcs embedded
type Ifaces {
IMutIfaces
AnotherFunc()
// Func() // This SHOULD NOT compile, since we embedded an immutable Func early.
}
type My struct {
name string
}
// here we go
// +immutable
func (my *My) Name() string {
return my.name
}
type You struct {
My
name string
}
func (you *You) YourName() string {
return you.name
}
// This SHOULD NOT compile, too.
//func (you *You) Name() string {
// return you.name
//}
We DO NOT need this. You should use un-exported principle if you do not want any one changing it somewhere. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 8, 2018
I really don't think that comments should affect the specification of the language, we'd probably want a separate construct for this
deanveloper
commented
Oct 8, 2018
|
I really don't think that comments should affect the specification of the language, we'd probably want a separate construct for this |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mcspring
Oct 8, 2018
Contributor
|
Maybe the `@immutable` annotation style is the right way. I think it only a
compile mark for definitions, but not the syntax of runtime. The `const T`
and `const *T` styles are instruive for golang.
…On Mon, Oct 8, 2018 at 11:36 AM Dean Bassett ***@***.***> wrote:
I really don't think that comments should affect the specification of the
language, we'd probably want a separate construct for this
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#27975 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAAd4iAYBEUiIPxkC5qzaUg_f3E2I_6zks5uisgggaJpZM4XE4if>
.
--
*Best Regards!*
*- Spring*
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
beoran
Oct 8, 2018
Go already has some significant comments. Although it may not have been a good idea to include those...
beoran
commented
Oct 8, 2018
|
Go already has some significant comments. Although it may not have been a good idea to include those... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 8, 2018
Technically speaking - the Go language itself does not have significant comments, but the go build tool will analyze comments to see if it should compile it or not, the go generate tool will analyze comments to generate code, etc. But these are only tools, and the Go language itself does not recognize build/generation tags.
In terms of @ tags, maybe if they're different than other languages, the typical styles don't fit with Go IMO. Maybe something like type string = []byte@immut?
deanveloper
commented
Oct 8, 2018
|
Technically speaking - the Go language itself does not have significant comments, but the In terms of @ tags, maybe if they're different than other languages, the typical styles don't fit with Go IMO. Maybe something like |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mcspring
Oct 8, 2018
Contributor
The type string = []byte@immut is as same as the
@immutable
type string = []bytehere. The syntax former is a little stranger to myself.
|
The @immutable
type string = []bytehere. The syntax former is a little stranger to myself. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 8, 2018
The issue is that it isn't the same though. That annotation relates to the defined type string rather than the aliased type.
You're saying "I'm defining an immutable type, string, which is the same as []byte" rather than "I'm defining a type, string, which is the same as an immutable []byte"
Also, that syntax cannot be inlined. So I can make a variable like var list []string@immut which cannot be done with the prefix-based syntax (remember that the immutability is a property of the type, not the variable, so the tag should be on the type)
deanveloper
commented
Oct 8, 2018
|
The issue is that it isn't the same though. That annotation relates to the defined type You're saying "I'm defining an immutable type, string, which is the same as []byte" rather than "I'm defining a type, string, which is the same as an immutable []byte" Also, that syntax cannot be inlined. So I can make a variable like |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 8, 2018
@mcspring
Annotations are not only ugly, they also target symbols instead of types and imply transitive immutability, which is not what we want because they'd make mixed-mutability types such as immut * mut T and immut [][] mut *T impossible which makes the whole concept limiting and rather useless.
This proposal is about the immutability of types and mutability qualifiers are language keywords used in type expressions.
Think about immutable types as read-only interfaces to potentially shared memory.
romshark
commented
Oct 8, 2018
•
|
@mcspring This proposal is about the immutability of types and mutability qualifiers are language keywords used in type expressions. Think about immutable types as read-only interfaces to potentially shared memory. |
romshark commentedOct 2, 2018
•
edited
This issue describes a language feature proposal to Immutable Types. It targets the current Go 1.x (> 1.11) language specification and doesn't violate the Go 1 compatibility promise. It also describes an even better approach to immutability for a hypothetical, backward-incompatible Go 2 language specification.
The linked Design Document describes the entire proposal in full detail, including the current problems, the benefits, the proposed changes, code examples an the FAQ.
Updates
const-poisoning, verbosity,const-keyword overloading and others.Introduction
Immutability is a technique used to prevent mutable shared state, which is a very common source of bugs, especially in concurrent environments, and can be achieved through the concept of immutable types.
Bugs caused by mutable shared state are not only hard to find and fix, but they're also hard to even identify. Such kind of problems can be avoided by systematically limiting the mutability of certain objects in the code. But a Go 1.x developer's current approach to immutability is manual copying, which lowers runtime performance, code readability, and safety. Copying-based immutability makes code verbose, imprecise and ambiguous because the intentions of the code author are never clear. Documentation can be rather misleading and doesn't solve the problems either.
Immutable Types in Go 1.x
Immutable types can help achieve this goal more elegantly improving the safety, readability, and expressiveness of the code. They're based on 5 fundamental rules:
These rules can be enforced by making the compiler scan all objects of immutable types for illegal modification attempts, such as assignments and calls to mutating methods and fail the compilation. The compiler would also need to check, whether types correctly implement immutable interface methods.
To prevent breaking Go 1.x compatibility this document describes a backward-compatible approach to adding support for immutable types by overloading the
constkeyword (see here for more details) to act as an immutable type qualifier.Immutable types can be used for:
Immutable Types in Go 2.x
Ideally, a safe programming language should enforce immutability by default where all types are immutable unless they're explicitly qualified as mutable because forgetting to make an object immutable is easier, than accidentally making it mutable. But this concept would require significant,
backward-incompatible language changes breaking existing Go 1.x code. Thus such an approach to immutability would only be possible in a new backward-incompatible Go 2.x language specification.
Related Proposals
This proposal is somewhat related to:
Detailed comparisons to other proposals are described in the design document, section 5..
Please feel free to file issues and pull requests, become a stargazer,
contact me directly at roman.sharkov@qbeon.com and join the conversation on Slack Gophers (@romshark), the international and the russian Telegram groups, as well as the original golangbridge, reddit and hackernews posts! Thank you!