-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: spec: immutable type qualifier #27975
Comments
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 |
@networkimprov please no |
As far as I can tell in my initial skim, this doesn't solve the Other comments:
|
@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? |
@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. |
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. |
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). |
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 |
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 changed This 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! |
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. |
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.
@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. |
@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 |
@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
|
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 |
@ianlancetaylor |
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. |
I haven't seen any comments from those who've given a 👎, so here's my input. 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. |
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 |
I didn't mean to imply it was the only time people used |
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. |
I'm not sure why you're bringing up Mutable variables can safely be accepted as |
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 |
@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 - |
@concastinator A number of people from the Go team have responded on this issue. If you just want a response, I think you have one. Indeed, you have several. As far as "level of interest leading to implementation," then, no, there is no level of interest that in itself leads to implementation. There also has to be an idea that works well with the rest of the language. All language changes have costs as well as benefits. As I've explained in several comments above I think that this particular proposal has some significant issues that make it a poor fit for Go. I personally would like to see a language or analyzer change that addresses at least some of the issues involving immutability in Go, but I have not yet seen a change that seems to have benefits that outweigh the costs. That is just my own personal judgement, of course. |
Hi, I found this proposal just now, I’m using Go for only half a year now. Previously I used the D programming language for more than ten years and C even longer. Version 2 of the D programming language introduced a const/immutable feature very similar to the one suggested here, I have worked with it for years, so I’d like to speak a bit from experience. Don’t do it. Just don’t. You are opening a can of worms you’ll never be able to close again. A
Here are a few examples of complications introduced by qualifiers. // library code
func f(p *const int) {/* … */}
// user code
func g(f func(*int)) {/* … */}
g(f) // Will this compile? It should!
func f(p *int) {/* … */}
func g(p *const int) {/* … */}
var a = [...]func(*int){f, g} // Will this compile? It should! What about this case? In D2 the func f(a *inout int) *inout int {/* … */}
Also, I’ve encountered situations which type S struct{
p *const int
}
(s *S) Set(p *const int) {
s.p = p
}
(s *const S) Get() *const int {
return s.p
} Suppose we pass an How will this work with a type assertion or switch? Will an Finally, the burden imposed on the programmer it cannot be underestimated how much unnecessary bureaucracy type qualifiers bear because of the incompatibilities they cause if not used with much care and far-sighted planning. That is not to mention the potential for bikeshedding in code review. Last words. I have used C and D earlier, each for many years. Changing to Go was an indescribable joy from the beginning because so many things causing subtle bugs and unnecessary interference with the actual task of implementing functionality have finally been done right. Simple things are reliably simple and safe. The absence of type qualifiers is a big part of it. So don’t do the same mistake. Leave type qualifiers out. [Edit] Bonus: interfaces as an impression of the additional burden of decisions Only Should I’m happy to give pages of more examples if interest is there. |
@edvdavid Thanks for the report. That is very helpful. |
Yes, thank you. Very thorough, thoughtful, and helpful. Having watched const qualifiers arrive in C a long time ago, and all the trouble they caused, just like the ones you list here, I would have hoped subsequent language designers would leave const-ness out of their type systems. We did that for Go, but we were not language designers, just programmers designing a language. |
I do most of my professional coding in F#, but I also use GO. Here's my 2 cents on this proposal. As a long time F# programmer, I vote for immutability by default with explicit use of a mut keyword. I've developed/maintained/refactored dozens of enterprise critical F# applications in the last decade. From my experience, having immutability by default truly does improve code simplicity/readability, code safety/correctness (especially with concurrent programming), and ease of refactoring. I use the F# mutable keyword sparingly. When I do use it, it's for local/private use only. I think it's great that go is getting generics and would like to see immutability by default some day. I do understand that adding too many new language features, especially those with limited real world benefit, is counter productive to the original GO language design goals. Given the GO language promise of backwards compatibility, perhaps immutability by default could be implemented in the future via an opt-in compiler option (maybe in conjunction with the compiler's -lang version option). Lastly, thank you to the GO team for all your hard work. |
I agree with psantoro: immutability by default is safer and allows for more optimizations. |
I think that immutability by default is definitely the right way to go. However, that would be an extremely large change, as well as not being backwards-compatible. |
In my experience, especially if it makes for a better experience overall, backwards compatibility tends to be overrated. To put this into an analogy everyone can understand, so many of the issues people associate with the Microsoft operating system which don’t exist on Mac are directly related to backwards compatibility. Meanwhile, Apple is not afraid to say “Yeah, the previous system may have been good for its time and it might be inconvenient for some developers to make the updates but it’s a one-time cost and makes for a better system for the ultimate users.” |
I largely disagree here. The last thing we would want is a "python 2 vs python 3" stagnation, granted a bit more went into that other than a backward compatibility issue. In terms of Java, they made some backward-incompatible changes between LTS releases (8 and 11). We are now on Java 17 and a huge portion of companies (even modern ones) are still using Java 8. Sure, it is a "one-time cost". However, we cannot feasibly promise backwards compatibility, then require nearly every library author, tool author, company, and really every user of the Go language, to rewrite all of their code. Sure, we could say that they don't need to rewrite their applications, and that libraries written in older versions of Go would have all exposed functions and types be imported as if they were all |
As I tried to show in my speech above, I don’t think it is possible to add useful backwards-compatible immutability to a system which has been designed based on mutability by default. To be useful and not harmful, immutability is too deep a structural difference. C++ keeps backwards compatibility at all costs, at least the last time I looked at it, including astronomic compilation times because the preprocessor is still supported and obvious nonsense such as In general, backwards compatibility has always a scalability limit beyond which it adds more damage than benefit. Obstructing progress and innovation can quickly be a higher price to pay than starting from scratch. For example, for backwards compatibility reasons, both the mass ounce and the mass ton have each three different definitions. I always thought of Go as a shining example of favouring innovation and progress over backwards compatibility – one could still do everything in C, with perfection in backwards compatibility as well as unreadable code when using a C library to manage multiplexed I/O with coroutines and synchronising FIFOs. Go, the most Unixian programming language since C, breaks holy C/Unix compatibilities such as pointer arithmetic ( |
Go may break compatibility with C, but it does not break compatibility with itself. See https://golang.org/doc/go1compat. I believe this is essential for Go's success. |
@ianlancetaylor As far as I can tell, that document doesn’t actually say Go will never break backwards compatibility. |
From my perspective of mainly using C, and only occasionally Go, there is really only one scenario where I make much use of 'const', that is for a assigning a name to an expression within a block which I then know can not change later in the block. This is for aid in comprehension, especially when coming back to the code some time later, so something like: void function (void)
{
...
while ( some_condition() ) {
...
bool const is_a_green_fish = is_a_fish(x) && is_green(x);
...
/* some later expression uses 'is_a_green_fish' */
}
...
} Hence this is a variable which can not be reassigned later in the block, and I'm expecting a later compiler error to enforce that such that I reduce the mental state I'm carrying while reading code. I suspect that could be achieved with a 'let' (or maybe 'once') keyword operating just like 'var' but with the semantic check phase of the compiler enforcing the no subsequent reassignment constraint, but I suspect there may be niggles with reference types. Or possibly just allowing the existing 'const' to be used within a function to assign from an arbitrary expression (e.g. another function call), not just from a compile time constant expression. i.e. in the following both 'j' and 'xyz' would be treated as if they had been declared 'var', with the difference that a subsequent reassignment would be a compiler error: func something() int {
return 8
}
func main() {
for i := 0; i < 3; i++ {
const j = i + 3
fmt.Println("i: ", j)
}
const xyz = 2 + something()
fmt.Println("xyz: ", xyz)
} The only other places where I occasionally use const in C is for an object to be allocated in read only memory by the linker (file scope definitions), and sometimes in leaf functions if it can aid documentation / comprehension, bearing in mind that one can escape const-ness on structs even without casts. It is the latter which is in part desired here, but one can never trust it anyway, so it has little real value, and the aforementioned poisoning issue. As to mutable vs non mutable data, I actually quite liked the way that worked in the Objective-C Foundation classes, with paired classes. However that does depend upon classful inheritance behaviour for calling immutable methods on a mutable value. The option to optimise the compiler code generation for methods with non pointer signatures such that they can pass by reference under the covers, while still preserving pass by value semantics sounds helpful. i.e. the method would simply use the implicit pointer in the ABI as long as no assignments occurred to the value, if the value was assigned to, then code would be generated such that a complete or partial copy could be made within the method itself - so preserving pass by value semantics. |
Perhaps not, but it takes a clear engineering position: Go will only break backward compatibility for an awfully good reason. By comparison, C has not broken backward compatibility since the first C standard in 1989. C++ has not broken backward compatibility since the first C++ standard in 1998, if we ignore the effect of additional keywords. Jave has, to the best of my knowledge, never broken backward compatibility at all. This approach has clearly worked well for these languages, all of which remain very popular. I think that Go should aspire to the same level of dependability. |
Java is planning on breaking certain unsafe reflection operations "in the future", but haven't done it yet. Currently, performing one of these unsafe operations will print a message to stderr. |
There are already many related proposals, and I think here is a better place to put my thoughts on. First I don't think using same keyword for different concepts is a good idea, as other languages have showed. There are as least several concepts mixed here, immutable vs readonly view, and use them as storage class vs type qualifier. For their differences, I like D's description. For Go's So I would like to extent For readonly views, D use the Take The constrain is scoped, so the return value's writability is the same as before calling constrains.ReadOnly, not as the referenced passing in argument. This way, a return subslice of a readonly view of a slice can be writable, if it's writable before the call, which usually is what the caller want. Like in Avoid using Other properties mentioned in #24889, like Edit:
|
I personally feel immutabilty must be added to go , even if not be default.we could introduce a new keyword |
As a huge fan of the simplicity that's currently made available by Go, I'll throw my two cents in the pot. While it's clear the proposer spent a lot of time and effort working this up, this is really just a matter of adding syntactic sugar really. To the core team, thank you so much for being so determined to keep the language simple, whilst listening to your community. Go is an amazing programming language. I use it every day at work, and hope that the language stays as simple as possible as the years progress. Let's not let the features win, haha! Absolute features corrupt absolutely. |
PLEASE add immutability. more important than generics were imho. this would make a huge diff in large software projects where there exists no strong way of ensuring data is read-only |
Hi, chiming in to provide current woes of a somewhat large Go code base (>1,000 packages with ~2million lines not including dependencies), currently struggling with accidental mutation. We currently have situations where a struct contains a map (or slice or some other pointer-backed data) which is widely used throughout the codebase. At some point, that struct is passed to a utility which believes it is the owner of that instance of the struct and mutates it in some way (writes to a map, appends/writes to a slice, changes a pointer etc). This causes those mutations to be seen by other holders of that variable. This bugs are very difficult to track down and often present as unexpected and surprising behavior requiring a lot of time spent debugging. We've had to resort to deep copying these maps/slices/etc so that each caller does not mutate another's held copy accidentally. For large objects, this can balloon memory and be rather slow (it routinely shows up in our pprof graphs). It would be much better to have the option to enforce read-only structs/pointers/etc. It also causes a bit of a dependency nightmare where you must ensure that any sub-struct contained within a struct is also responsibly doing a deepcopy. I'm not a huge fan of the proposed syntax, however. I find it interesting that Go has already solved this problem for channels via type myStruct struct {
names map[string]string
}
func readOnly(m <-myStruct) {
readOnlyMapFunction(m.names)
...
}
func readOnlyMapFunction(names <-map[string]string) {
...
} in which reads would be allowed, but writes expressly denied. This does present the problem of const-poisoning as others have mentioned. But even in a codebase as large as the one I work in, I would take that tradeoff. |
With the usage of var a! int
a! = 3 // Error.
a!, b := f()
a! = 3 // Error.
b = 3 // Allowed.
a! := 3
b := a! // Allowed because simple types copy.
a! := "An example."
b := a! // Allowed because strings are also immutable anyways.
a! := []byte("An example.")
b := a! // Error.
var a A
b! := a // Allowed for everything because immutability is only a guarantee that that variable can't be used to change something, not a guarantee that it won't change ever at all.
// Returns can't be immutable because of how defer works.
func (a! A) Im(b! B) (c C) {
return a! + b!
}
p := &a! // Error. Can't store address of immutable variable in mutable variable.
p! := &a! // Allowed.
var a! struct { b! int } // Error. Struct fields can't individually be immutable.
var a! struct { p *int }
p := a!.p // Error. a!.p is immutable just like a! is.
p! := a!.p // Allowed.
*p! = 3 // Error.
m! := make(map[any]any)
f(m![3]) // Allowed.
m[3] = 2 // Error.
m2 := m! // Error.
var a! [3]int
s := a![:] // Error.
type A int
func (a A) M()
var a! A
a!.M() // Allowed.
type A int
func (a *A) M()
var a! A
a!.M() // Error.
type A struct { p *int }
func (a A) M()
var a! A
a!.M() // Error. And so on. Unfortunately, those last ones mean that without code migrating to this manually, that feature just won't be too useful, as making a variable immutable would limit you to only being able to call methods that expect it to be possible for it to be immutable. Existing code that hasn't changed its receivers to have a |
ImmuGo Branch of Go... or Go compiler option👀 All references are immutable by default like in Rust unless declared mutable. Silly idea? (yes, i havent thought this through and don't know enough about the subject to do so) |
Hey @DeedleFake, just wanted to point out that if instead of For example, let's say var a int
a = 3 // Error.
a, b_ := f()
a = 3 // Error.
b_ = 3 // Allowed.
a := 3
b_ := a // Allowed because simple types copy.
a := "An example."
b_ := a // Allowed because strings are also immutable anyways.
a := []byte("An example.")
b_ := a // Error.
var a_ A
b := a_ // Allowed for everything because immutability is only a guarantee that that variable can't be used to change something, not a guarantee that it won't change ever at all.
// Returns can't be immutable because of how defer works.
func (a A) Im(b B) (c_ C) {
return a + b
}
p_ := &a // Error. Can't store address of immutable variable in mutable variable.
p := &a // Allowed.
var a struct { b int } // Error. Struct fields can't individually be immutable.
var a! struct { p_ *int }
p_ := a.p_ // Error. a.p_ is immutable just like a! is.
p := a.p_ // Allowed.
*p = 3 // Error.
m := make(map[any]any)
f(m[3]) // Allowed.
m[3] = 2 // Error.
m2_ := m // Error.
var a [3]int
s_ := a[:] // Error.
type A int
func (a_ A) M()
var a A
a.M() // Allowed.
type A int
func (a_ *A) M()
var a A
a.M() // Error.
type A struct { p_ *int }
func (a_ A) M()
var a A
a.M() // Error. |
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 and 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
const
keyword (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 [email protected] 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!
The text was updated successfully, but these errors were encountered: