├── README.md
└── document_header.png
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Go - Immutability
4 | This document describes a language feature proposal to immutability for the [Go
5 | programming language](https://golang.org). The proposed feature targets the
6 | current [Go 1.x (> 1.11) language specification](https://golang.org/ref/spec)
7 | and doesn't violate [the Go 1 compatibility
8 | promise](https://golang.org/doc/go1compat). It also describes [an even better
9 | approach to immutability](#3-immutability-by-default-go--2x) for a hypothetical, backward-incompatible [Go 2
10 | language specification](https://blog.golang.org/toward-go2).
11 |
12 |
28 |
29 | **Table of Contents**
30 | - [Go - Immutability](#go---immutability)
31 | - [1. Introduction](#1-introduction)
32 | - [1.1. Current Problems](#11-current-problems)
33 | - [1.1.1. Ambiguous Code and Dangerous Bugs](#111-ambiguous-code-and-dangerous-bugs)
34 | - [1.1.2. Vague and Bloated Documentation](#112-vague-and-bloated-documentation)
35 | - [1.1.3. The - Slow but Safe vs Dangerous but Fast - Dilemma](#113-the---slow-but-safe-vs-dangerous-but-fast---dilemma)
36 | - [1.1.4. Inconsistent Concept of Constants](#114-inconsistent-concept-of-constants)
37 | - [1.2. Benefits](#12-benefits)
38 | - [1.2.1. Safe and Precise Code](#121-safe-and-precise-code)
39 | - [1.2.2. Self-Explaining Code](#122-self-explaining-code)
40 | - [1.2.3. Increased Runtime Performance](#123-increased-runtime-performance)
41 | - [2. Proposed Language Changes](#2-proposed-language-changes)
42 | - [2.1. Immutable Fields](#21-immutable-fields)
43 | - [2.2. Immutable Methods](#22-immutable-methods)
44 | - [2.3. Immutable Arguments](#23-immutable-arguments)
45 | - [2.4. Immutable Return Values](#24-immutable-return-values)
46 | - [2.5. Immutable Variables](#25-immutable-variables)
47 | - [2.6. Immutable Interface Methods](#26-immutable-interface-methods)
48 | - [2.7. Slice Aliasing](#27-slice-aliasing)
49 | - [2.8. Address Operators](#28-address-operators)
50 | - [2.8.1. Taking the address of a variable](#281-taking-the-address-of-a-variable)
51 | - [2.8.2. Dereferencing a pointer](#282-dereferencing-a-pointer)
52 | - [2.9. Immutable Reference Types](#29-immutable-reference-types)
53 | - [2.9.1. Pointer Examples](#291-pointer-examples)
54 | - [2.9.1.1. Immutable pointer to a mutable object](#2911-immutable-pointer-to-a-mutable-object)
55 | - [2.9.1.2. Mutable pointer to an immutable object](#2912-mutable-pointer-to-an-immutable-object)
56 | - [2.9.1.3. Immutable pointer to an immutable object](#2913-immutable-pointer-to-an-immutable-object)
57 | - [2.9.2. Slice Examples](#292-slice-examples)
58 | - [2.9.2.1. Immutable slice of immutable objects](#2921-immutable-slice-of-immutable-objects)
59 | - [2.9.2.2. Mutable slice of immutable objects](#2922-mutable-slice-of-immutable-objects)
60 | - [2.9.2.3. Immutable slice of mutable objects](#2923-immutable-slice-of-mutable-objects)
61 | - [2.9.2.4. Mutable slice of mutable objects](#2924-mutable-slice-of-mutable-objects)
62 | - [2.9.3. Map Examples](#293-map-examples)
63 | - [2.9.3.1. Mutable map of immutable keys to mutable objects](#2931-mutable-map-of-immutable-keys-to-mutable-objects)
64 | - [2.9.3.2. Mutable map of mutable keys to immutable objects](#2932-mutable-map-of-mutable-keys-to-immutable-objects)
65 | - [2.9.3.3. Mutable map of immutable keys to immutable objects](#2933-mutable-map-of-immutable-keys-to-immutable-objects)
66 | - [2.9.3.4. Immutable map of immutable keys to immutable objects](#2934-immutable-map-of-immutable-keys-to-immutable-objects)
67 | - [2.9.4. Channel Examples](#294-channel-examples)
68 | - [2.9.4.1. Immutable channels of immutable objects](#2941-immutable-channels-of-immutable-objects)
69 | - [2.9.4.2. Immutable channels of mutable objects](#2942-immutable-channels-of-mutable-objects)
70 | - [2.9.4.3. Mutable channels of immutable objects](#2943-mutable-channels-of-immutable-objects)
71 | - [2.10. Immutable Package-Scope Variables](#210-immutable-package-scope-variables)
72 | - [2.11. Explicit Type Casting](#211-explicit-type-casting)
73 | - [2.11.1. Simple Cast](#2111-simple-cast)
74 | - [2.11.2. Literal Type Casting](#2112-literal-type-casting)
75 | - [2.11.3. Prohibition of Casting Immutable- to Mutable Types](#2113-prohibition-of-casting-immutable--to-mutable-types)
76 | - [2.12. Implicit Casting](#212-implicit-casting)
77 | - [2.12.1. Implicit Casting of Pointer Receivers](#2121-implicit-casting-of-pointer-receivers)
78 | - [2.12. Standard Library](#212-standard-library)
79 | - [3. Immutability by Default (Go >= 2.x)](#3-immutability-by-default-go--2x)
80 | - [3.1. Benefits](#31-benefits)
81 | - [3.1.1. Safety by Default](#311-safety-by-default)
82 | - [3.1.2. Less Code](#312-less-code)
83 | - [3.1.3. No `const` Keyword Overloading](#313-no-const-keyword-overloading)
84 | - [4. FAQ](#4-faq)
85 | - [4.1. Are the items within immutable slices/maps also immutable?](#41-are-the-items-within-immutable-slicesmaps-also-immutable)
86 | - [4.2. Go is all about simplicity, so why make the language more complicated?](#42-go-is-all-about-simplicity-so-why-make-the-language-more-complicated)
87 | - [4.3. Aren't other features such as generics and better error handling not more important right now?](#43-arent-other-features-such-as-generics-and-better-error-handling-not-more-important-right-now)
88 | - [4.4. Why overload the `const` keyword instead of introducing a new keyword like `immutable` etc.?](#44-why-overload-the-const-keyword-instead-of-introducing-a-new-keyword-like-immutable-etc)
89 | - [4.5. How are constants different from immutable types?](#45-how-are-constants-different-from-immutable-types)
90 | - [4.6. Why do we need immutable receivers if we already have copy-receivers?](#46-why-do-we-need-immutable-receivers-if-we-already-have-copy-receivers)
91 | - [4.7. Why do we need immutable interface types?](#47-why-do-we-need-immutable-interface-types)
92 | - [4.8. Doesn't the `const` qualifier add boilerplate and make code harder to read?](#48-doesnt-the-const-qualifier-add-boilerplate-and-make-code-harder-to-read)
93 | - [4.9. Why do we need the distinction between immutable and mutable reference types?](#49-why-do-we-need-the-distinction-between-immutable-and-mutable-reference-types)
94 | - [4.10. Why implicitly cast mutable to immutable types?](#410-why-implicitly-cast-mutable-to-immutable-types)
95 | - [4.11. Can't these problems be solved by a linter?](#411-cant-these-problems-be-solved-by-a-linter)
96 | - [5. Other Proposals](#5-other-proposals)
97 | - [5.1. proposal: spec: add read-only slices and maps as function arguments #20443](#51-proposal-spec-add-read-only-slices-and-maps-as-function-arguments-20443)
98 | - [5.1.1. Disadvantages](#511-disadvantages)
99 | - [5.1.2. Similarities](#512-similarities)
100 | - [5.2. proposal: Go 2: read-only types #22876](#52-proposal-go-2-read-only-types-22876)
101 | - [5.2.1. Disadvantages](#521-disadvantages)
102 | - [5.2.2. Differences](#522-differences)
103 | - [5.2.3. Similarities](#523-similarities)
104 |
105 | ## 1. Introduction
106 | Immutability is a technique used to prevent mutable shared state, which is a
107 | very common source of bugs, especially in concurrent environments, and can be
108 | achieved through the concept of **immutable types**.
109 |
110 | Bugs caused by mutable shared state are not only hard to find and fix, but
111 | they're also hard to even identify. Such kind of problems can be avoided by
112 | systematically limiting the mutability of certain objects in the code. But a Go
113 | 1.x developer's current approach to immutability is manual copying, which lowers
114 | runtime performance, code readability, and safety. Copying-based immutability
115 | makes code verbose, imprecise and ambiguous because the intentions of the code
116 | author are never clear.
117 |
118 | **Immutable types** can help achieve this goal more elegantly improving the
119 | safety, readability, and expressiveness of the code. They're based on 5
120 | fundamental rules:
121 |
122 | - **I.** Each and every type has an immutable counterpart.
123 | - **II.** Assignments to objects of an immutable type are illegal.
124 | - **III.** Calls to mutating methods (methods with a mutable receiver type) on
125 | objects of an immutable type are illegal.
126 | - **IV.** Mutable types can be cast to their immutable counterparts, but not the
127 | other way around.
128 | - **V.** Immutable interface methods must be implemented by a method with an
129 | immutable receiver type.
130 |
131 | These rules can be enforced by making the compiler scan all objects of immutable
132 | types for illegal modification attempts, such as assignments and calls to
133 | mutating methods and fail the compilation. The compiler would also need to
134 | check, whether types correctly implement immutable interface methods.
135 |
136 | Ideally, a safe programming language should enforce [immutability by
137 | default](#3-immutability-by-default-go--2x) where all types are immutable unless
138 | they're explicitly qualified as mutable because forgetting to make an object
139 | immutable is easier, then accidentally making it mutable. But this concept would
140 | require significant, backward-incompatible language changes breaking existing Go
141 | 1.x code. Thus such an approach to immutability would only be possible in a new
142 | backward-incompatible Go 2.x language specification.
143 |
144 | To prevent breaking Go 1.x compatibility this document describes a
145 | **backward-compatible** approach to adding support for immutable types by
146 | overloading the `const` keyword ([see here for more
147 | details](#44-why-overload-the-const-keyword-instead-of-introducing-a-new-keyword-like-immutable-etc))
148 | to act as an immutable type qualifier.
149 |
150 | ### 1.1. Current Problems
151 |
152 | The current approach to immutability (namely copying) has a number of
153 | disadvantages listed below and sorted by importance in descending order.
154 |
155 | #### 1.1.1. Ambiguous Code and Dangerous Bugs
156 | The absence of immutable types can lead to ambiguous code that results
157 | in dangerous, hard to find bugs. Consider the following method definition:
158 |
159 | ```go
160 | // Method ...
161 | func (r *T) Method(
162 | a *T,
163 | b *T,
164 | v []*T,
165 | ) (rv *T) {/*...*/}
166 | ```
167 | The above code is ambiguous, it doesn't represent the intentions of its original
168 | author:
169 | - Will it produce any side-effects on `r`?
170 | - Will it mutate the `T`s referenced by `a` and `b`?
171 | - Will it mutate `v`?
172 | - Will it mutate any `T` referenced by any item of `v`?
173 | - Is the `T` referenced by `rv` allowed to be mutated?
174 |
175 | All those questions can lead to bugs if they're not properly answered, and
176 | [documentation never answers them
177 | reliably](#112-vague-and-bloated-documentation).
178 |
179 | If the above function is exported from a 3-rd party package `xyz` that's
180 | imported to a project `P` as an external dependency and the documentation
181 | promises (or "claims") to not mutate any of the symbols, the code in `P` will be
182 | written with this assumption in mind.
183 |
184 | At any time the vendor of `xyz` might change its behavior, either intentionally
185 | or unintentionally, which will introduce bugs and data corruption:
186 | - `a`, `b`, `v` or any items of `v` might get aliased and mutated either
187 | directly (in the scope of `xyz.T.Method`) or indirectly (at an unspecified
188 | point in time).
189 | - New side-effects could be introduced to `r`.
190 | - `rv` might get aliased and introduce unwanted side-effects when mutated by the
191 | `xyz.T.Method` caller.
192 |
193 | In the worst case, the maintainers of `P` won't even be informed about the
194 | mutations that were unintentionally introduced to a newer version of
195 | `xyz.T.Method` through a bug in its implementation. But even if the vendors of
196 | `xyz` correctly update the changelog and the documentation introducing new
197 | intentional side-effects, chances are high that the maintainers of `P` miss the
198 | changes in the documentation and fail to react accordingly. `P` will continue to
199 | compile, but its **outputs will become corrupted** which can't always be easily
200 | detected even in the presence of extensive automated testing.
201 |
202 | #### 1.1.2. Vague and Bloated Documentation
203 | Documentation never represents **actual** intentions, it represents **claimed**
204 | intentions. Claimed intentions can't be relied on, because claims are not
205 | guaranteed to remain in sync with actual code behavior.
206 |
207 | To avoid ambiguous code developers often describe *mutability recommendations*
208 | of variables, fields, arguments, methods and return values. Not only does this
209 | unnecessarily complicate and bloat up the documentation, but it also makes it
210 | error-prone and redundant. Documentation can easily get out of sync with the
211 | actual code because it can't be verified algorithmically.
212 |
213 | #### 1.1.3. The - Slow but Safe vs Dangerous but Fast - Dilemma
214 | Copies are the only way to achieve immutability in Go 1.x, but copies inevitably
215 | degrade runtime performance. This dilemma encourages Go 1.x developers to either
216 | write unsafe mutable APIs when targeting optimal runtime performance or safe but
217 | slow and copy-code bloated ones.
218 |
219 | Unfortunatelly, optimal performance and code safety are currently mutually
220 | exclusive, even though having both would be possible with compiler-enforced
221 | immutable types at the cost of a slightly decreased compilation time.
222 |
223 | #### 1.1.4. Inconsistent Concept of Constants
224 | Currently, Go 1.x won't allow non-scalar constants such as constant slices:
225 | ```
226 | const each2 = []byte{'e', 'a', 'c', 'h'} // Compile-time error
227 | ```
228 |
229 | [Robert Griesemer](https://github.com/griesemer) stated in his comment to
230 | [proposal #6386](https://github.com/golang/go/issues/6386) that this is by
231 | language design, quote:
232 | > This is neither a defect of the language nor the design. The language was
233 | _deliberately_ designed to only permit constant of basic types.
234 |
235 | But many developers still claim it to be a major design flaw because exclusive
236 | immutability for scalar types only leads to inconsistency in the language
237 | design. What most developers really need is not *constants of arbitrary types*
238 | but rather **immutable package-scope variables**, which can be implemented
239 | consistently with the help of immutable types:
240 | ```
241 | var each2 const []byte = []byte{'e', 'a', 'c', 'h'}
242 | ```
243 | Even though technically `each2` is not a *constant* but an *immutable
244 | package-scope variable* - it solves the mutability problem.
245 |
246 | ### 1.2. Benefits
247 | Support for immutable types would provide the benefits listed below and sorted
248 | by importance in descending order.
249 |
250 | #### 1.2.1. Safe and Precise Code
251 | Immutable types make APIs less ambiguous.
252 |
253 | With immutable types the situations described in the [previous
254 | section](#111-ambiguous-code-and-dangerous-bugs) wouldn't even be possible,
255 | because the author of the function of the external package would need to
256 | explicitly qualify immutable types as such to make the compiler enforce the
257 | guarantee:
258 |
259 | ```go
260 | // Method ...
261 | func (r * const T) Method(
262 | a * const T,
263 | b * T,
264 | v const [] * const T,
265 | ) (
266 | rv * const T,
267 | rv2 * T,
268 | ) {
269 | /*...*/
270 | }
271 | ```
272 | The above code is unambiguous and precise. It clearly represent the intentions
273 | of its original author and answers all critical questions reliably:
274 | - Will it produce any side-effects on `r`?
275 | - **No, it can't. Its receiver is immutable**
276 | - Will it mutate the `T` referenced by `a`?
277 | - **No, it can't. The `T` referenced by `a` is immutable**
278 | - Will it mutate `v`?
279 | - **No, it can't. `v` is immutable**
280 | - Will it mutate any `T` referenced by any item of `v`?
281 | - **No, it can't. The `T`s referenced by any item of `v` are immutable**
282 | - Is the `T` referenced by `rv` allowed to be mutated?
283 | - **No, it's not. The `T` referenced by `rv` is immutable**
284 | - Will it mutate the `T` referenced by `b`?
285 | - **Yes, it potentially will!**
286 | - Is the `T` referenced by `rv2` allowed to be mutated?
287 | - **Yes, it won't lead to unwanted side-effects**
288 |
289 | Whenever a mutable type is taken, returned or provided it's assumed that its
290 | state will potentially be mutated.
291 |
292 | The user of this function would make decisions based on the actual function
293 | definition in the code instead of relying on the potentially inconsistent
294 | documentation.
295 |
296 | If the vendors of this function decide to change the mutability of either an
297 | input or output type or the mutability of the object the method operates on -
298 | they will have to change the type introducing breaking API changes causing
299 | compiler errors and making the user pay attention to whether or not everything's
300 | right.
301 |
302 | The vendors won't be able to just silently introduce mutations causing bugs! The
303 | compiler will prevent this from happening either before the vendors release the
304 | update (assuming that the code is compiled by a CI/CD system before publication)
305 | or during the user's local build in the worst case.
306 |
307 | #### 1.2.2. Self-Explaining Code
308 | With immutable types, there's no need to explicitly describe mutability
309 | recommendations in the documentation. When immutable types are declared as such
310 | then the code becomes self-explaining:
311 | - An **argument** or a **variable** of an immutable type can be relied on not
312 | being changed neither inside the scope it's declared in, nor in the scopes
313 | it's passed to.
314 | - An immutable **method** (or a "function with a **receiver** of an immutable
315 | type" if you will) - can be relied on not changing the object it operates on.
316 | - A **return value** of an immutable type can be relied on not being changed by
317 | the function caller.
318 | - A **field** of an immutable type can be relied on not being changed as soon as
319 | the object is initialized, even inside the scope of its origin package.
320 |
321 | #### 1.2.3. Increased Runtime Performance
322 | Immutability provides a way to safely avoid unnecessary copying as well as
323 | unnecessary indirections through mutable and immutable interfaces (because
324 | interfaces do have a cost).
325 |
326 | Immutability also makes specific compiler optimizations possible. Whether or not
327 | those optimization opportunities are exploited later on is rather irrelevant to
328 | this particular proposal.
329 |
330 | ## 2. Proposed Language Changes
331 | The language must be adjusted to support the `const` qualifier inside type
332 | definitions to qualify certain types as immutable.
333 |
334 | The compiler must enforce the following rules:
335 | - Immutable types are declared with the `const` qualifier prepended.
336 | - Assignments to objects of an immutable type are illegal.
337 | - Calls to mutating methods (methods with a mutable receiver type) on
338 | objects of an immutable type are illegal.
339 | - Calls to mutating interface methods on immutable interface references
340 | are illegal.
341 | - Immutable types cannot be cast to their mutable counterparts.
342 | - Types must implement immutable interface methods using an immutable receiver
343 | type.
344 | - Mutable types are implicitly cast to their immutable counterparts.
345 | - During method calls - pointer receivers must be implicitly cast in both
346 | directions (allowing immutable to mutable cast) if the types of the objects
347 | they're pointing to are equal.
348 |
349 | It is to be noted, that all proposed changes are fully backward-compatible and
350 | don't require any breaking changes to be introduced to the Go 1.x language
351 | specification. Code written in previous versions of Go 1.x will continue to
352 | compile and work as usual.
353 |
354 | ### 2.1. Immutable Fields
355 | Struct fields of an immutable type can only be set during the object
356 | initialization and are then immutable for the entire lifetime of the object
357 | within any context.
358 |
359 | ```go
360 | type Object struct {
361 | ImmutableField const * const Object // Immutable
362 | MutableField *Object // Mutable
363 | }
364 |
365 | // MutatingMethod is a non-const method
366 | func (o *Object) MutatingMethod() {
367 | o.MutableField = &Object{}
368 | o.ImmutableField = &Object{} // Compile-time error
369 | }
370 |
371 | func main() {
372 | // Immutable fields are immutable once the object is initialized
373 | obj := Object{
374 | ImmutableField: &Object{
375 | ImmutableField: nil,
376 | },
377 | }
378 |
379 | obj.MutableField = &Object{}
380 | obj.ImmutableField = nil // Compile-time error
381 | obj.ImmutableField.ImmutableField = &Object{} // Compile-time error
382 | }
383 | ```
384 | **Expected compilation errors:**
385 | ```
386 | .example.go:9:23 cannot assign to immutable field (Object.ImmutableField) of o (type const * const Object)
387 | .example.go:21:25 cannot assign to immutable field (Object.ImmutableField) of obj (type const * const Object)
388 | .example.go:22:40 cannot assign to contextually immutable field (Object.ImmutableField) of obj (type const * const Object)
389 | ```
390 |
391 | ----
392 |
393 | ### 2.2. Immutable Methods
394 | Immutable methods are declared using the `const` qualifier on the
395 | function-receiver and guarantee to not mutate the receiver in any way when
396 | called. They can safely be used in immutable contexts, such as within other
397 | immutables methods and/or on immutable objects.
398 |
399 | Technically, this feature should rather be called "immutable function
400 | receivers".
401 |
402 | ```go
403 | type Object struct {
404 | mutableField *Object // Mutable
405 | }
406 |
407 | // MutatingMethod is a non-const method.
408 | func (o *Object) MutatingMethod() const * const Object {
409 | o.mutableField = &Object{}
410 | return o.ImmutableMethod()
411 | }
412 |
413 | // ImmutableMethod is a const method.
414 | // It's illegal to mutate any fields of the receiver.
415 | // It's illegal to call mutating methods of the receiver
416 | func (o * const Object) ImmutableMethod() const * const Object {
417 | o.MutatingMethod() // Compile-time error
418 | o.mutableField = &Object{} // Compile-time error
419 | o.mutableField.mutableField = &Object{} // Compile-time error
420 | return o.mutableField
421 | }
422 |
423 | func main() {
424 | obj := * const Object (&Object{})
425 | obj.ImmutableMethod()
426 | obj.MutatingMethod() // Compile-time error
427 | }
428 | ```
429 | **Expected compilation errors:**
430 | ```
431 | .example.go:15:7 cannot call mutating method (Object.MutatingMethod) on immutable o (type * const Object)
432 | .example.go:16:21 cannot assign to contextually immutable field (Object.mutableField) of o (type * const Object)
433 | .example.go:17:34 cannot assign to contextually immutable field (Object.mutableField) of o (type * const Object)
434 | .example.go:24:9 cannot call mutating method (Object.MutatingMethod) on immutable obj (type * const Object)
435 | ```
436 |
437 | ----
438 |
439 | ### 2.3. Immutable Arguments
440 | Immutable types can be used to guarantee the immutability of function
441 | arguments.
442 |
443 | ```go
444 | type Object struct {
445 | MutableField *Object // Mutable
446 | }
447 |
448 | // MutatingMethod is a non-const method
449 | func (o *Object) MutatingMethod() {}
450 |
451 | // ImmutableMethod is a const method
452 | func (o * const Object) ImmutableMethod() {}
453 |
454 | // MutateObject mutates the given object
455 | func MutateObject(obj *Object) {
456 | obj.MutableField = &Object{}
457 | }
458 |
459 | // ReadObj is guaranteed to only read the object passed by the argument
460 | // without mutating it in any way
461 | func ReadObj(
462 | obj * const Object // Mutable reference to immutable object
463 | ) {
464 | obj = nil // fine, because the pointer is mutable
465 |
466 | MutateObject(obj) // Compile-time error
467 | obj.MutatingMethod() // Compile-time error
468 | obj.MutableField = &Object{} // Compile-time error
469 | }
470 | ```
471 | **Expected compilation errors:**
472 | ```
473 | .example.go:23:19 cannot use obj (type * const Object) as type *Object in argument to MutateObject
474 | .example.go:24:9 cannot call mutating method (Object.MutatingMethod) on immutable obj (type * const Object)
475 | .example.go:25:23 cannot assign to contextually immutable field (Object.MutableField) of obj (type * const Object)
476 | ```
477 |
478 | ----
479 |
480 | ### 2.4. Immutable Return Values
481 | Immutable types can be used to guarantee the immutability of a
482 | function's returned values.
483 |
484 | ```go
485 | type Object struct {
486 | MutableField *Object // Mutable
487 | }
488 |
489 | // MutatingMethod is a non-const method
490 | func (p *Object) MutatingMethod() {
491 | p.MutableField = &Object{}
492 | }
493 |
494 | // ReturnImmutable returns an immutable value
495 | func ReturnImmutable() const * const Object {
496 | return &Object{
497 | MutableField: &Object{
498 | MutableField: &Object{},
499 | },
500 | }
501 | }
502 |
503 | func main() {
504 | immutableVariable := ReturnImmutable()
505 |
506 | immutableVariable.MutableField = &Object{} // Compile-time error
507 | immutableVariable.MutatingMethod() // Compile-time error
508 | }
509 | ```
510 | **Expected compilation errors:**
511 | ```
512 | .example.go:22:37 cannot assign to contextually immutable field (Object.MutableField) of immutable immutableVariable (type const * const Object)
513 | .example.go:23:23 cannot call mutating method (Object.MutatingMethod) on immutable immutableVariable (type const * const Object)
514 | ```
515 |
516 | ----
517 |
518 | ### 2.5. Immutable Variables
519 | Immutable types can be used to declare immutable variables.
520 |
521 | ```go
522 | type Object struct {
523 | MutableField *Object // Mutable
524 | }
525 |
526 | // MutatingMethod is a non-const method
527 | func (o *Object) MutatingMethod() {}
528 |
529 | // ImmutableMethod is a const method
530 | func (o * const Object) ImmutableMethod() {}
531 |
532 | // NewObject creates and returns a new mutable object instance
533 | func NewObject() *Object {
534 | return &Object{}
535 | }
536 |
537 | // MutateObject mutates the given object
538 | func MutateObject(obj *Object) {
539 | obj.MutableField = &Object{}
540 | }
541 |
542 | // ConstRef helps shortening declaration statements
543 | type ConstRef const * const Object
544 |
545 | func main() {
546 | // The definition version:
547 | // The cast is necessary because NewObject returns a mutable value
548 | // while we want an immutable variable
549 | obj := const * const Object (NewObject())
550 |
551 | // The var declaration version:
552 | // (this statement could be shortened using a type alias)
553 | var obj_var_long const * const Object = NewObject()
554 | var obj_var ConstRef = NewObject()
555 |
556 | obj.MutableField = &Object{} // Compile-time error
557 | obj.MutatingMethod() // Compile-time error
558 | MutateObject(obj) // Compile-time error
559 | }
560 | ```
561 | **Expected compilation errors:**
562 | ```
563 | .example.go:23:23 cannot assign to contextually immutable field (Object.MutableField) of obj (type const * const Object)
564 | .example.go:24:9 cannot call mutating method (Object.MutatingMethod) on immutable obj (type const * const Object)
565 | .example.go:25:19 cannot use obj (type const * const Object) as type *Object in argument to MutateObject
566 | ```
567 |
568 | ### 2.6. Immutable Interface Methods
569 | Interfaces can be obliged to require receiver type immutability using the
570 | `const` qualifier in the method declaration to prevent the implementing function
571 | from mutating the object referenced by the interface.
572 | ```go
573 | // Interface represents a strict interface with an immutable method
574 | type Interface interface {
575 | // Read must not mutate the underlying implementation
576 | const Read() string
577 |
578 | // Write can potentially mutate the underlying implementation
579 | Write(string)
580 | }
581 |
582 | // ValidImplementation represents a correct implementation of Interface
583 | type ValidImplementation struct {
584 | /*...*/
585 | }
586 |
587 | // Read correctly implements Interface.Read, it has an immutable receiver
588 | func (r * const ValidImplementation) Read() string {
589 | /*...*/
590 | }
591 |
592 | // Write correctly implements Interface.Write,
593 | // even though the receiver is immutable
594 | func (r * const ValidImplementation) Write(s string) {
595 | /*...*/
596 | }
597 |
598 | // InvalidImplementation represents an incorrect implementation of Interface
599 | type InvalidImplementation struct {
600 | /*...*/
601 | }
602 |
603 | // Read incorrectly implements the immutable Interface.Read,
604 | // the receiver must be of type: * const InvalidImplementation
605 | func (r * InvalidImplementation) Read() string {
606 | /*...*/
607 | }
608 |
609 | // Write correctly implements Interface.Write
610 | func (r * InvalidImplementation) Write(s string) {
611 | /*...*/
612 | }
613 |
614 | func main() {
615 | var iface Interface = &InvalidImplementation{} // Compile-time error
616 | iface.Write(0, "example")
617 |
618 | var readOnlyIface const Interface = &ValidImplementation{}
619 | readOnlyIface.Read()
620 | readOnlyIface.Write() // Compile-time error
621 | }
622 | ```
623 | ```
624 | .example.go:43:26: cannot use InvalidImplementation literal (type *InvalidImplementation) as type Interface in assignment:
625 | *InvalidImplementation does not implement Interface (Read method has mutable pointer receiver, expected an immutable receiver type)
626 | .example.go:48:19: cannot call mutating method (Interface.Write) on immutable readOnlyIface (type const Interface)
627 | ```
628 |
629 | ----
630 |
631 | ### 2.7. Slice Aliasing
632 | Immutability of slices is always inherited from their parent slice. Sub-slicing
633 | immutable slices results in new immutable slices:
634 | ```go
635 | func ReturnConstSlice() const []int {
636 | return []int {1, 2, 3}
637 | }
638 |
639 | func main() {
640 | originalSlice := const([]int{1, 2, 3})
641 | subSlice := originalSlice[:1]
642 |
643 | originalSlice[0] = 4 // Compile-time error
644 | subSlice[0] = 4 // Compile-time error
645 |
646 | anotherSubSlice := ReturnConstSlice()[:1]
647 | anotherSubSlice[0] = 4 // Compile-time error
648 | }
649 | ```
650 | ```
651 | .example.go:9:23 cannot assign to immutable originalSlice (type const []int)
652 | .example.go:10:18 cannot assign to immutable subSlice (type const []int)
653 | .example.go:13:25 cannot assign to immutable anotherSubSlice (type const []int)
654 | ```
655 |
656 | ### 2.8. Address Operators
657 |
658 | #### 2.8.1. Taking the address of a variable
659 | Taking the address of an *immutable* variable results in a *mutable* pointer to
660 | an *immutable* object:
661 |
662 | ```go
663 | var t const T = T{}
664 | t_pointer := &t // * const T
665 | ```
666 |
667 | To take an *immutable* pointer from an *immutable* variable explicit casting is
668 | necessary:
669 | ```go
670 | var t const T = T{}
671 | t_pointer1 := const(&t) // const * const T
672 |
673 | // Or like this:
674 | t_pointer2 := const * const T(&t) // const * const T
675 | ```
676 |
677 | #### 2.8.2. Dereferencing a pointer
678 | Dereferencing an *immutable* pointer to an *immutable* object:
679 | ```go
680 | t := const * const T (&T{})
681 | *t // const T
682 | ```
683 |
684 | Dereferencing a mutable pointer to an *immutable* object:
685 | ```go
686 | t := * const T (&T{})
687 | *t // const T
688 | ```
689 |
690 | Dereferencing an *immutable* pointer to a *mutable* object:
691 | ```go
692 | t := const * T (&T{})
693 | *t // T
694 | ```
695 |
696 | ### 2.9. Immutable Reference Types
697 | Reference types such as [slices](https://golang.org/ref/spec#Slice_types),
698 | [maps](https://golang.org/ref/spec#Map_types),
699 | [channels](https://golang.org/ref/spec#Channel_types) and
700 | [pointers](https://golang.org/ref/spec#Pointer_types) can also be declared
701 | immutable using the `const` qualifier just like any other type. But the objects
702 | / items referenced by immutable reference types **don't inherit their
703 | immutability!** Reference types can point to both mutable and immutable types,
704 | this makes the type system very versatile and flexible.
705 |
706 | The examples below demonstrate a few possible combinations:
707 |
708 | #### 2.9.1. Pointer Examples
709 |
710 | ##### 2.9.1.1. Immutable pointer to a mutable object
711 |
712 | ```go
713 | var immut2mut const *Object = &Object{}
714 |
715 | immut2mut = &Object{} // Compile-time error
716 | immut2mut.Field = 42 // fine
717 | immut2mut.Mutation() // fine
718 | ```
719 |
720 | ##### 2.9.1.2. Mutable pointer to an immutable object
721 |
722 | ```go
723 | var mut2immut * const Object = &Object{}
724 |
725 | mut2immut = &Object{} // fine
726 | mut2immut.Field = 42 // Compile-time error
727 | mut2immut.Mutation() // Compile-time error
728 | ```
729 |
730 | ##### 2.9.1.3. Immutable pointer to an immutable object
731 | ```go
732 | var immut2immut const * const Object = &Object{}
733 |
734 | immut2immut = &Object{} // Compile-time error
735 | immut2immut.Field = 42 // Compile-time error
736 | immut2immut.Mutation() // Compile-time error
737 | ```
738 |
739 | #### 2.9.2. Slice Examples
740 |
741 | ##### 2.9.2.1. Immutable slice of immutable objects
742 |
743 | ```go
744 | var immut2immut const [] const Object
745 | immut2immut = append(immut2immut, Object{}) // Compile-time error
746 | immut2immut[0] = Object{} // Compile-time error
747 |
748 | obj := immut2immut[0]
749 | obj.Mutation() // Compile-time error
750 | ```
751 |
752 | ##### 2.9.2.2. Mutable slice of immutable objects
753 |
754 | ```go
755 | var mut2immut [] const Object
756 | mut2immut = append(mut2immut, Object{}) // fine
757 | mut2immut[0] = Object{} // fine
758 |
759 | obj := mut2immut[0]
760 | obj.Mutation() // Compile-time error
761 | ```
762 |
763 | ##### 2.9.2.3. Immutable slice of mutable objects
764 |
765 | ```go
766 | var immut2mut const [] Object
767 | immut2mut = append(immut2mut, Object{}) // Compile-time error
768 | immut2mut[0] = Object{} // Compile-time error
769 |
770 | obj := immut2mut[0]
771 | obj.Mutation() // fine
772 | ```
773 |
774 | ##### 2.9.2.4. Mutable slice of mutable objects
775 |
776 | ```go
777 | var mut2mut [] Object
778 | mut2mut = append(mut2mut, Object{}) // fine
779 | mut2mut[0] = Object{} // fine
780 |
781 | obj := mut2mut[0]
782 | obj.Mutation() // fine
783 | ```
784 |
785 | #### 2.9.3. Map Examples
786 |
787 | ##### 2.9.3.1. Mutable map of immutable keys to mutable objects
788 |
789 | ```go
790 | var mut_immut2mut map[const Object] Object
791 |
792 | newKey := Object{}
793 | mut_immut2mut[newKey] = Object{} // fine
794 | delete(mut_immut2mut, newKey) // fine
795 |
796 | for key, value := range mut_immut2mut {
797 | key.Mutation() // Compile-time error
798 | value.Mutation() // fine
799 | }
800 | ```
801 |
802 | ##### 2.9.3.2. Mutable map of mutable keys to immutable objects
803 |
804 | ```go
805 | var mut_mut2immut map[Object] const Object
806 |
807 | newKey := Object{}
808 | mut_mut2immut[newKey] = Object{} // fine
809 | delete(mut_mut2immut, newKey) // fine
810 |
811 | for key, value := range mut_mut2immut {
812 | key.Mutation() // fine
813 | value.Mutation() // Compile-time error
814 | }
815 | ```
816 |
817 | ##### 2.9.3.3. Mutable map of immutable keys to immutable objects
818 |
819 | ```go
820 | var immut_immut2immut map[const Object] const Object
821 |
822 | newKey := Object{}
823 | immut_immut2immut[newKey] = Object{} // fine
824 | delete(immut_immut2immut, newKey) // fine
825 |
826 | for key, value := range immut_immut2immut {
827 | key.Mutation() // Compile-time error
828 | value.Mutation() // Compile-time error
829 | }
830 | ```
831 |
832 | ##### 2.9.3.4. Immutable map of immutable keys to immutable objects
833 |
834 | ```go
835 | var m const map[const Object] const Object
836 |
837 | newKey := Object{}
838 | m[newKey] = Object{} // Compile-time error
839 | delete(m, newKey) // Compile-time error
840 |
841 | for key, value := range m {
842 | key.Mutation() // Compile-time error
843 | value.Mutation() // Compile-time error
844 | }
845 | ```
846 |
847 | #### 2.9.4. Channel Examples
848 |
849 | ##### 2.9.4.1. Immutable channels of immutable objects
850 |
851 | ```go
852 | func main() {
853 | ch := ConstReadOnlyChannel()
854 | ch = AnotherChannelOfSameType() // Compile-time error
855 |
856 | immutObj := <-ch
857 | immutObj.Field = 42 // Compile-time error
858 | immutObj.Mutation() // Compile-time error
859 | }
860 |
861 | // ConstReadOnlyChannel returns an immutable read only channel
862 | // of immutable objects
863 | func ConstReadOnlyChannel() const <-chan const Object {
864 | ch := make(chan Object)
865 | go func() {
866 | ch <- Object{}
867 | }()
868 | return ch
869 | }
870 | ```
871 |
872 | ##### 2.9.4.2. Immutable channels of mutable objects
873 |
874 | ```go
875 | func main() {
876 | ch := ConstReadOnlyChannel()
877 | ch = AnotherChannelOfSameType() // Compile-time error
878 |
879 | mutObj := <-ch
880 | mutObj.Field = 42 // fine
881 | mutObj.Mutation() // fine
882 | }
883 |
884 | // ConstReadOnlyChannel returns an immutable read only channel
885 | // of mutable objects
886 | func ConstReadOnlyChannel() const <-chan Object {
887 | ch := make(chan Object)
888 | go func() {
889 | ch <- Object{}
890 | }()
891 | return ch
892 | }
893 | ```
894 |
895 | ##### 2.9.4.3. Mutable channels of immutable objects
896 |
897 | ```go
898 | func main() {
899 | ch := MutReadOnlyChannel()
900 | ch = MutReadOnlyChannel() // fine
901 |
902 | immutObj := <-ch
903 | immutObj.Field = 42 // Compile-time error
904 | immutObj.Mutation() // Compile-time error
905 | }
906 |
907 | // MutReadOnlyChannel returns a mutable read only channel of immutable objects
908 | func MutReadOnlyChannel() <-chan const Object {
909 | ch := make(chan Object)
910 | go func() {
911 | ch <- Object{}
912 | }()
913 | return ch
914 | }
915 | ```
916 |
917 | ### 2.10. Immutable Package-Scope Variables
918 | Package-scope variables of an immutable type can be used similarly to
919 | package-scope constants. They compensate for the [lack of non-scalar
920 | constants](https://github.com/romshark/Go-1-2-Proposal---Immutability#114-inconsistent-concept-of-constants).
921 |
922 | ```go
923 | package library
924 |
925 | type T struct {}
926 |
927 | func (t * const T) MutatingMethod() {
928 | /*...*/
929 | }
930 |
931 | // ConstantNames represents a package-scope immutable slice of strings
932 | var ConstantNames const []string = []string{"Anna", "Mike", "Ashley"}
933 |
934 | // privateImmutTInstances represents a package-scope immutable slice of pointers
935 | // to immutable instances of T
936 | var privateImmutTInstances const [] * const T = []*T{
937 | &T{},
938 | &T{},
939 | &T{},
940 | }
941 |
942 | // Function represents an exported function that tries to mutate immutable
943 | // package-scope variables
944 | func Function() {
945 | ConstantNames[0] = "Hannah" // Compile-time error
946 | privateImmutTInstances[0] = &T{} // Compile-time error
947 | constT := privateImmutTInstances[0]
948 | constT.MutatingMethod() // Compile-time error
949 | }
950 | ```
951 | ```
952 | .library.go:23:23: cannot assign to immutable ConstantNames (type const []string)
953 | .library.go:24:32: cannot assign to immutable privateImmutTInstances (type const [] * const T)
954 | .library.go:26:12: cannot call mutating method (T.MutatingMethod) on immutable constT (type * const T)
955 | ```
956 |
957 | Imported:
958 | ```go
959 | package main
960 |
961 | import "github.com/x/library"
962 |
963 | func main() {
964 | library.ConstantNames[0] = "Hannah" // Compile-time error
965 | }
966 | ```
967 | ```
968 | .library.go:6:31: cannot assign to immutable library.ConstantNames (type const []string)
969 | ```
970 |
971 | ### 2.11. Explicit Type Casting
972 | Mutable types are always implicitly cast to their immutable counterparts but in
973 | some situations explicit casting may also be useful.
974 |
975 | #### 2.11.1. Simple Cast
976 | Simple typecasting `const(mt)` converts a mutable type into its immutable
977 | counterpart:
978 |
979 | ```go
980 | // Simple non-const to const casting
981 | const_string := const("test") // const string
982 |
983 | const_pointer := const(&T{}) // const * T
984 |
985 | const_slice := const([]int{1, 2, 3}) // const []int
986 | }
987 | ```
988 |
989 | Applying simple typecasting to already immutable types has no effect.
990 |
991 | #### 2.11.2. Literal Type Casting
992 | For more complex types simple `const` casting is insufficient, thus a literal
993 | type cast `immutable type (symbol)` to an immutable type is required.
994 |
995 | ```go
996 | var original_slice [] [] *T
997 |
998 | // Mutable slice of immutable slices of pointers to a mutable instance of T
999 | s1 := [] const [] * T (original_slice)
1000 |
1001 | // Immutable slice of mutable slices of pointers to an immutable instance of T
1002 | s2 := const [] [] * const T (original_slice)
1003 |
1004 | // Mutable slices of mutable slices of pointers to an immutable instance of T
1005 | s3 := [] [] * const T (original_slice)
1006 |
1007 | // Immutable slice of immutable slices of pointers to an immutable instance of T
1008 | s4 := const [] const [] * const T (original_slice)
1009 |
1010 | var original_map map[*T]*T
1011 |
1012 | // Immutable map of:
1013 | // pointers to an immutable instance of T (key) to:
1014 | // pointers to a mutable instance of T (value)
1015 | m1 := const map [* const T] * T (original_map)
1016 |
1017 | // Immutable map of:
1018 | // pointers to a mutable instance of T (key) to:
1019 | // pointers to an immutable instance of T (value)
1020 | m2 := const map [* T] * const T (original_map)
1021 |
1022 | // Mutable map of:
1023 | // pointers to an immutable instance of T (key) to:
1024 | // pointers to an immutable instance of T (value)
1025 | m3 := map [* const T] * const T (original_map)
1026 | ```
1027 |
1028 | #### 2.11.3. Prohibition of Casting Immutable- to Mutable Types
1029 | Casting immutable types to mutable types is forbidden because it would make it
1030 | possible to silently void the immutability guarantee breaking the entire concept
1031 | of immutability.
1032 |
1033 | ### 2.12. Implicit Casting
1034 | Mutable types are implicitly cast to their immutable counterparts. This rule is
1035 | applied to any type in a type-chain. If we consider the definition of a type as
1036 | a binary sequence where mutable types are represented by `0` and immutable types
1037 | by `1`, then any conversions of `1` to `0` should cause a compile-time error.
1038 |
1039 | ```go
1040 | // tip: use an 80-column wide view to make sense of the markers
1041 |
1042 | // 0_ 0_ 0_ 0 1______
1043 | var origin [] [] [] * const T
1044 |
1045 | /* LEGAL CONVERSIONS */
1046 |
1047 | // 1_______ 0_ 0_ 0 1______
1048 | var var1 const [] [] [] * const T = origin // 00001 -> 10001
1049 |
1050 | // 0_ 0_ 1_______ 0 0
1051 | var var2 [] [] const [] * T = origin // 00001 -> 00100
1052 |
1053 | // 0_ 1_______ 1_______ 0 0
1054 | var var3 [] const [] const [] * T = origin // 00001 -> 01100
1055 |
1056 | /* ILLEGAL CONVERSIONS */
1057 |
1058 | // 0_ 1_______ 0_ 0 0 F F
1059 | var inv1 [] const [] [] * T = origin // 00001 -> 01000
1060 |
1061 | // 1_______ 1_______ 1_______ 1______ 0 F F
1062 | var inv2 const [] const [] const [] const * T = origin // 00001 -> 11110
1063 | ```
1064 |
1065 | #### 2.12.1. Implicit Casting of Pointer Receivers
1066 | Pointer receivers are implicitly cast in both directions (mutable to immutable
1067 | and vice-versa) when the types they're pointing to are equal.
1068 |
1069 | Methods:
1070 | ```go
1071 | type T struct {/*...*/}
1072 | func (r1 *T) M1() {/*...*/}
1073 | func (r2 const * T) M2() {/*...*/}
1074 | func (r3 * const T) M3() {/*...*/}
1075 | func (r4 const * const T) M4() {/*...*/}
1076 | ```
1077 |
1078 | Variables:
1079 | ```go
1080 | // mutable pointer to mutable T
1081 | t1 := &T{}
1082 |
1083 | // immutable pointer to mutable T
1084 | var t2 const * T = &T{}
1085 |
1086 | // mutable pointer to immutable T
1087 | var t3 * const T = &T{}
1088 |
1089 | // immutable pointer to immutable T
1090 | var t4 const * const T = &T{}
1091 | ```
1092 |
1093 | | Combination | Compile-time Result | Reason |
1094 | |-|-|-|
1095 | | `t1.M1()` | legal | types match. |
1096 | | `t2.M1()` | **implicit cast** | `const * T` (`t2`) is implicitly cast to `* T` (`r1`) because in both cases `T` is mutable. |
1097 | | `t3.M1()` | illegal | `T` referenced by `t3` is immutable, but `M1` is a mutating method. |
1098 | | `t4.M1()` | illegal | `T` referenced by `t4` is immutable, but `M1` is a mutating method. |
1099 | | `t1.M2()` | **implicit cast** | `* T` (`t1`) is implicitly cast to `const * T` (`r2`) because in both cases `T` is mutable. |
1100 | | `t2.M2()` | legal | types match. |
1101 | | `t3.M2()` | illegal | `T` referenced by `t3` is immutable, but `M2` is a mutating method. |
1102 | | `t4.M2()` | illegal | `T` referenced by `t4` is immutable, but `M2` is a mutating method. |
1103 | | `t1.M3()` | **implicit cast** | `* T` (`t1`) is implicitly cast to `* const T` (`r3`) because `T` is mutable and `M3` is a non-mutating method. |
1104 | | `t2.M3()` | illegal | `T` referenced by `t2` is mutable, but `M3` is a mutating method. |
1105 | | `t3.M3()` | legal | types match. |
1106 | | `t4.M3()` | **implicit cast** | `const * const T` (`t4`) is implicitly cast to `* const T` (`r3`) because in both cases `T` is immutable. |
1107 | | `t1.M4()` | **implicit cast** | `* T` (`t1`) is implicitly cast to `const * const T` (`r4`) because `T` is mutable and `M3` is a non-mutating method. |
1108 | | `t2.M4()` | **implicit cast** | `const * T` (`t2`) is implicitly cast to `const * const T` (`r4`) because `T` is mutable and `M4` is a non-mutating method. |
1109 | | `t3.M4()` | **implicit cast** | `* const T` (`t3`) is implicitly cast to `const * const T` (`r4`) because in both cases `T` is immutable. |
1110 | | `t4.M4()` | legal | types match. |
1111 |
1112 | ### 2.12. Standard Library
1113 | Minimal backward-compatible changes to the standard library need to be made to
1114 | make user-written code that takes advantage of immutable types interoperable
1115 | with the standard library.
1116 |
1117 | [strings.Join](https://golang.org/pkg/strings/#Join) is a typical example of a
1118 | standard library function that needs to be updated to take advantage of
1119 | immutable types. Its updated version would have to guarantee the immutability of
1120 | `a`:
1121 |
1122 | ```go
1123 | func Join(a const []string, sep string) string
1124 | ```
1125 |
1126 | Optionally, `sep` could be declared immutable as well (`const string`)
1127 | protecting it from accidental overwrites in the function scope. Making `sep`
1128 | immutable simply guides the function's implementor by telling him/her that `sep`
1129 | is not meant to be overwritten in the function's scope, it has no meaning for
1130 | the function's caller though.
1131 |
1132 | This change is necessary because otherwise, the following code that takes
1133 | advantage of immutable types would not compile:
1134 |
1135 | ```go
1136 | // Example won't compile because the old strings.Join takes a mutable slice,
1137 | // but casting the immutable "a" to a mutable slice of strings is illegal!
1138 | func Example(a const []string) {
1139 | concat := strings.Join(a, ",") // Compile-time error
1140 | }
1141 | ```
1142 |
1143 | If [strings.Join](https://golang.org/pkg/strings/#Join) won't support immutable
1144 | types, then its users will be **forced** to fall back to a mutable slice
1145 | argument, which makes the immutability concept useless for their specific case.
1146 |
1147 | ## 3. Immutability by Default (Go >= 2.x)
1148 | If we were to think of an immutability proposal for the backward-incompatible Go
1149 | 2 language specification, then making all types immutable by default and
1150 | introducing a special keyword `mut` for mutability qualification would be a
1151 | better option.
1152 |
1153 | ```go
1154 | // Object implements the ObjectInterface interface
1155 | type Object struct {
1156 | Immutable_str string
1157 | Mutable_str mut string
1158 |
1159 | Immutable_immutRef_to_immutObj * Object
1160 | Mutable_mutRef_to_immutObj mut * Object
1161 | Mutable_mutRef_to_mutObj mut * mut Object
1162 |
1163 | Immutable_immutSlice_of_immutObj [] Object
1164 | Mutable_mutSlice_of_immutObj mut [] Object
1165 | Mutable_mutSlice_of_mutObj mut [] mut Object
1166 |
1167 | Immutable_immutMap_of_immutObj map[Object] Object // immutable key
1168 | Mutable_mutMap_of_immutObj mut [Object] Object // immutable key
1169 | Mutable_mutMap_of_mutObj mut [mut Object] mut Object // mutable key
1170 | }
1171 |
1172 | // MutableMethod implements ObjectInterface.MutableMethod
1173 | func (mutableReceiver * mut Object) MutableMethod(
1174 | mutableArgument mut * Object, // mutable reference to immutable object
1175 | ) (
1176 | mutableReturnValue mut * mut Object, // mutable reference to mutable object
1177 | ) {
1178 | var mutRef_to_mutObj mut * mut Object
1179 | var mutRef_to_immutObj mut * Object
1180 | var immutRef_to_immutObj * Object
1181 |
1182 | return nil
1183 | }
1184 |
1185 | // ImmutableMethod implements ObjectInterface.ImmutableMethod
1186 | func (immutableReceiver *Object) ImmutableMethod(
1187 | immutableArgument * Object, // immutable reference to immutable object
1188 | ) (
1189 | immutableReturnValue * Object // immutable reference to immutable object
1190 | ) {
1191 | var mutRef_to_mutObj mut * mut Object
1192 | var mutRef_to_immutObj mut * Object
1193 | var immutRef_to_immutObj * Object
1194 |
1195 | return nil
1196 | }
1197 |
1198 | type ObjectInterface interface {
1199 | mut MutableMethod(arg mut * Object) (returnValue mut * mut Object)
1200 | ImmutableMethod(arg *Object) (returnValue *Object)
1201 | }
1202 | ```
1203 |
1204 | ### 3.1. Benefits
1205 |
1206 | #### 3.1.1. Safety by Default
1207 | It's easy to forget to add the `const` qualifier and accidentally make something
1208 | mutable. But when mutable types need to be explicitly declared mutable using the
1209 | `mut` qualifier writing code becomes even safer.
1210 |
1211 | #### 3.1.2. Less Code
1212 | Statistically, Most of the variables, arguments, fields, return values and
1213 | methods are immutable, thus the frequent `const` qualifiers can be replaced by
1214 | fewer `mut` qualifiers, which improves both readability and coding speed. The
1215 | `mut` keyword is also shorter than `const`.
1216 |
1217 | #### 3.1.3. No `const` Keyword Overloading
1218 | The need for overloading of the `const` keyword would vanish, which would
1219 | improve semantic language consistency.
1220 |
1221 | ## 4. FAQ
1222 |
1223 | ### 4.1. Are the items within immutable slices/maps also immutable?
1224 | **No**, they're not! As stated in [Section 2.9.](#29-immutable-reference-types),
1225 | an immutable slice/map of mutable objects is declared this way:
1226 | ```go
1227 | type ImmutableSlice const []*Object
1228 | type ImmutableMap const map[*Object]*Object
1229 | ```
1230 | If you want the items of an immutable slice/map to be immutable as well, you'll
1231 | need to declare them using the `const` qualifier:
1232 | ```go
1233 | type ImmutableSlice const [] const * const Object
1234 | type ImmutableMap const map[*Object] const * const Object
1235 | type ImmutableMapAndKey const map[const * const Object] const * const Object
1236 | ```
1237 | A deeply-immutable matrix could, therefore, be declared the following way:
1238 | ```go
1239 | type ImmutableMatrix const [] const [] int
1240 | ```
1241 |
1242 | ---
1243 |
1244 | ### 4.2. Go is all about simplicity, so why make the language more complicated?
1245 | The `const` qualifier adds only a little cognitive overhead:
1246 | - When declaring a **function argument** we have to know whether we want to be
1247 | able to change its state and make it immutable if we don't.
1248 | - When declaring a **struct field** we have to know, whether we want the state
1249 | of this field to remain unchangeable, during the lifetime of an object
1250 | instantiated from this struct, as soon as it's initialized.
1251 | - When declaring a **return value** we have to know whether we want to give the
1252 | caller the permission to modify the object we returned.
1253 | - When declaring a **variable** we have to know, whether we want to change it
1254 | in this context.
1255 | - When declaring a **function-receiver** we have to know, whether this function
1256 | will change anything inside the receiver.
1257 | - When declaring an **interface method** we have to know, whether this method
1258 | should not change the state of the object implementing this interface.
1259 | - When declaring a **reference type** such as a pointer, a slice a map or a
1260 | channel we have to know whether we want to:
1261 | - make the object changeable, but not its reference
1262 | - make the actual reference changeable, but not the object it references
1263 | - make both the reference and the object changeable
1264 |
1265 | This additional cognitive overhead prevents us from introducing the complexity
1266 | created by mutable shared state. Bugs introduced through mutable shared state
1267 | are very dangerous, hard to notice, hard to identify and pretty hard to fix.
1268 | Justifying the simplicity of a language which can lead to very complex bugs is
1269 | rather incorrect when considering the insignificant overhead of the `const`
1270 | qualifier. Thus, immutability is a feature, the overhead of which outweighs the
1271 | disadvantages of not having it.
1272 |
1273 | **Example:** You always have to remember to copy stuff that you don't want
1274 | others to be able to mutate, or at least explicitly advise to "not mutate
1275 | certain stuff" in the documentation running the risk of breaking your
1276 | inattentive colleague's code:
1277 |
1278 | ```go
1279 | // ConnectedClients returns the list of all currently connected clients.
1280 | // DO NOT mutate the returned slice, this could break the server!
1281 | func (s *Server) ConnectedClients() []Client {
1282 | return s.clients
1283 | }
1284 | ```
1285 | But even if the people working with your code follow your advices, they could
1286 | still mess it up:
1287 | ```go
1288 | // ThisWontMutateIt verbally promises to
1289 | // not mutate the given slice of clients
1290 | func ThisWontMutateIt(clients []Client) {
1291 | // You know what? to hell with the promise!
1292 | // I don't know where this slice originated from, so why care?
1293 | clients[len(clients) - 1] = nil
1294 | }
1295 |
1296 | clients = server.ConnectedClients()
1297 | // It promised not to mutate it, so it's safe, right? right!?
1298 | ThisWontMutateIt(clients)
1299 | ```
1300 |
1301 | To improve the safety of the code above, we'd usually return a copy instead:
1302 | ```go
1303 | func (s *Server) ConnectedClients() []Client {
1304 | // Copy the client list to avoid returning an unsafe mutable reference
1305 | clients := make([]Client, len(s.clients))
1306 | for i, clt := range s.clients {
1307 | clients[i] = clt
1308 | }
1309 | return clients
1310 | }
1311 | ```
1312 | This certainly makes both code and documentation more complicated and error
1313 | prone (and slower) than it could be with immutability:
1314 | ```go
1315 | // ConnectedClients returns the list of all currently connected clients.
1316 | func (s * const Server) ConnectedClients() const []Client {
1317 | return s.clients
1318 | }
1319 | ```
1320 |
1321 | ---
1322 |
1323 | ### 4.3. Aren't other features such as generics and better error handling not more important right now?
1324 | Unlike other language specification issues such as *"generics"* and *"how to
1325 | handle errors more elegantly"* there's really not much to argue about in case of
1326 | immutability. It should be clear that:
1327 | - it makes code both safer and easier to make sense of,
1328 | - it doesn't require any breaking changes,
1329 | - it doesn't even require a single new language keyword.
1330 |
1331 | Therefore immutability should be considered of higher priority compared to other
1332 | language design proposals.
1333 |
1334 | ----
1335 |
1336 | ### 4.4. Why overload the `const` keyword instead of introducing a new keyword like `immutable` etc.?
1337 | **Backwards-compatibility**. Using the const keyword would allow us to introduce
1338 | immutability to Go 1.x without having to make breaking changes to the language.
1339 | The introduction of a new keyword could potentially break existing Go 1.x code,
1340 | where the new keyword might be used for naming symbols causing build conflicts.
1341 | `const` on the other hand is already a [reserved language
1342 | keyword](https://golang.org/ref/spec#Keywords) which doesn't interfere with the
1343 | proposed language changes and verbally comes close to the desired meaning (for
1344 | example, C++ uses the `const` keyword to do just that).
1345 |
1346 | ----
1347 |
1348 | ### 4.5. How are constants different from immutable types?
1349 | **Short:** Constants are static in memory, while immutable types are just
1350 | write-protected references to mutable memory.
1351 |
1352 | **Long:** The value of a constant is defined during the compilation and remains
1353 | a static piece of memory for the entire lifetime of your program. An immutable
1354 | field, argument, return value, receiver or variable, on the other hand, is
1355 | **not** static in memory, because it can still be mutated through mutable
1356 | references:
1357 |
1358 | ```go
1359 | // CreateList creates a new slice and returns both, a mutable and an immutable
1360 | // reference to it (which is bad! don't ever do that!
1361 | // unless you know what you're after!)
1362 | func CreateList(size int) (mutable []string, immutable const []string) {
1363 | newSlice := make([]string, size)
1364 | for i := 0; i < size; i++ {
1365 | newSlice[i] = "sample text"
1366 | }
1367 | return newSlice, newSlice
1368 | }
1369 |
1370 | func main() {
1371 | mutableReference, immutableReference := CreateList(10)
1372 |
1373 | // Mutating an immutable return value is illegal
1374 | immutableReference[5] = "mutated" // Compile-time error
1375 |
1376 | // Mutating the underlying array through a mutable reference is just fine!
1377 | mutableReference[5] = "mutated"
1378 |
1379 | // You can now observe the mutation from the read-only immutable reference
1380 | immutableReference[5] // "mutated"
1381 | }
1382 | ```
1383 |
1384 | **NOTICE:** the above code is **bad code**! Its purpose was to demonstrate that
1385 | immutable types are not constants. If you want to prevent immutable objects from
1386 | being mutated for sure - drop all mutable references to it as soon as it's
1387 | created!
1388 |
1389 | ----
1390 |
1391 | ### 4.6. Why do we need immutable receivers if we already have copy-receivers?
1392 | There are two reasons: safety and performance.
1393 |
1394 | **Copy-receivers don't prevent mutations!** They simply can't because of
1395 | [pointer aliasing](https://en.wikipedia.org/wiki/Pointer_aliasing):
1396 |
1397 | ```go
1398 | type Object struct {
1399 | name string
1400 | parent *Object
1401 | children []*Object
1402 | }
1403 |
1404 | // SetName is a non-const mutating method
1405 | func (o *Object) SetName(newName string) {
1406 | o.name = newName
1407 | }
1408 |
1409 | // ReadOnlyMethod is insidious because it verbally "promises"
1410 | // to not touch the object while it still can do!
1411 | // Even though it has a non-pointer receiver, the copy
1412 | // can't get rid of aliasing and can thus mutate internals!
1413 | func (o Object) ReadOnlyMethod() {
1414 | if len(o.children) > 0 {
1415 | // Mutating contextually immutable aliases is legal, VERY BAD!
1416 | o.children[0] = nil
1417 | }
1418 | if o.parent != nil {
1419 | // Call non-const method in immutable context is legal, VERY BAD!
1420 | o.parent.SetName("GOTCHA!")
1421 | }
1422 | }
1423 |
1424 | func main() {
1425 | obj := &Object{
1426 | name: "root",
1427 | parent: nil,
1428 | }
1429 | obj.children = []*Object{
1430 | &Object{
1431 | name: "child_1",
1432 | parent: obj,
1433 | },
1434 | &Object{
1435 | name: "child_2",
1436 | parent: obj,
1437 | },
1438 | }
1439 |
1440 | fmt.Println("Root before: ", obj)
1441 | fmt.Println("Child before: ", obj.children[0])
1442 |
1443 | firstChild := obj.children[0]
1444 | firstChild.ReadOnlyMethod() // Will mutate parent's name
1445 |
1446 | obj.ReadOnlyMethod() // Will mutate children list
1447 |
1448 | fmt.Println("Root after: ", obj) // Children list mutated
1449 | fmt.Println("Child after: ", firstChild) // Root name mutated
1450 | }
1451 | ```
1452 | https://play.golang.org/p/0kRSuVFkSMN
1453 |
1454 | **Copy-receivers are slow(er)**. Consider the following benchmark:
1455 | ```go
1456 | type Object struct {
1457 | name string
1458 | text []rune
1459 | double float64
1460 | integer int64
1461 | bytes []byte
1462 | }
1463 |
1464 | // PointerReceiver has a pointer receiver
1465 | func (o *Object) PointerReceiver() (string, []rune, float64) {
1466 | return o.name, o.text, o.double
1467 | }
1468 |
1469 | // CopyReceiver has a copy receiver
1470 | func (o Object) CopyReceiver() (string, []rune, float64) {
1471 | return o.name, o.text, o.double
1472 | }
1473 |
1474 | var name string
1475 | var text []rune
1476 | var double float64
1477 |
1478 | // BenchmarkPointerReceiver benchmarks the pointer-receiver method
1479 | func BenchmarkPointerReceiver(b *testing.B) {
1480 | obj := &Object{}
1481 | b.ResetTimer()
1482 | for n := 0; n < b.N; n++ {
1483 | name, text, double = obj.PointerReceiver()
1484 | }
1485 | }
1486 |
1487 | // BenchmarkCopyReceiver benchmarks the copy-receiver method
1488 | func BenchmarkCopyReceiver(b *testing.B) {
1489 | obj := &Object{}
1490 | b.ResetTimer()
1491 | for n := 0; n < b.N; n++ {
1492 | name, text, double = obj.CopyReceiver()
1493 | }
1494 | }
1495 | ```
1496 | https://play.golang.org/p/2xgn7YMosXO
1497 |
1498 | The results should be similar to:
1499 | ```
1500 | goos: windows
1501 | goarch: amd64
1502 | pkg: benchreceiver
1503 | BenchmarkPointerReceiver-12 1000000000 2.12 ns/op
1504 | BenchmarkCopyReceiver-12 300000000 4.23 ns/op
1505 | PASS
1506 | ok benchreceiver 4.110s
1507 | ```
1508 | _Windows 10; i7 3930K @ 3.80 Ghz_
1509 | ```
1510 | goos: darwin
1511 | goarch: amd64
1512 | pkg: github.com/romshark/benchreceiver
1513 | BenchmarkPointerReceiver-8 1000000000 2.05 ns/op
1514 | BenchmarkCopyReceiver-8 300000000 4.62 ns/op
1515 | PASS
1516 | ok benchreceiver 4.129s
1517 | ```
1518 | _MacOS High Sierra (10.13); i7-4850HQ @ 2.30 GHz_
1519 |
1520 | Even though ~2 nanoseconds doesn't sound like much it's still twice as
1521 | expensive to call.
1522 |
1523 | **Conclusion:** copy-receivers are not a solution, they make your code slower
1524 | **without** providing any protection from [pointer
1525 | aliasing](https://en.wikipedia.org/wiki/Pointer_aliasing), thus immutable
1526 | receivers (be it an immutable copy or an immutable pointer receiver) are
1527 | necessary to ensure compiler-enforced safety.
1528 |
1529 | ### 4.7. Why do we need immutable interface types?
1530 | Immutable interface types allow us to **reuse** interface types disabling their
1531 | mutating ability in certain scopes without having to define two separate
1532 | interface types (one interface type with read methods only and another one with
1533 | both mutating and non-mutating methods)
1534 |
1535 | Passing an immutable interface to a function as an argument while trying to call
1536 | a mutating method on it, for example, would generate a compile-time error:
1537 | ```go
1538 | type Interface {
1539 | const ReadOnly()
1540 | Write()
1541 | }
1542 |
1543 | type Implementation struct {}
1544 | func (i * const Implementation) ReadOnly() {}
1545 | func (i * Implementation) Write() {}
1546 |
1547 | // TakeReadInterface will not be able to execute non-const methods of the interface
1548 | func TakeReadInterface(iface const Interface) {
1549 | iface.ReadOnly() // fine
1550 | iface.Write() // Compile-time error
1551 | }
1552 |
1553 | func main() {
1554 | iface := &Implementation{}
1555 | TakeReadInterface(iface)
1556 | }
1557 | ```
1558 | ```
1559 | .example:13:11: cannot call mutating method (Interface.Write) on immutable iface (type const Interface)
1560 | ```
1561 |
1562 | ### 4.8. Doesn't the `const` qualifier add boilerplate and make code harder to read?
1563 | **Short answer**: No, it doesn't and it can be quite the opposite.
1564 |
1565 | **Long answer**: Let's pretend we need to write a method with the following
1566 | constraints:
1567 | - It must take a slice of pointers to objects of type `Object` as argument `s`.
1568 | - It must return all objects from an internal slice.
1569 | - It must use the function `Dependency` that's exported from a third-party
1570 | package `thirdparty` and pass `s` to it.
1571 | - The `thirdparty.Dependency` function doesn't specify whether or not it'll
1572 | mutate `s` in the documentation.
1573 | - It **must not mutate** `s`, neither the slice nor the referenced objects!
1574 | - It must ensure the internal slice **cannot be mutated** from the outside!
1575 | - It must ensure, that the receiver **is not mutated** in any way!
1576 |
1577 | Our current approach would be copying because there's no other way to ensure
1578 | immutability.
1579 | ```go
1580 | /* WITHOUT IMMUTABILITY */
1581 |
1582 | func (rec *T) OurMethod(s []*Object) [] *Object {
1583 | s_copy := make([] *Object, len(s))
1584 | for i, item := range s {
1585 | // Clone the items to get rid of aliasing
1586 | // Copying an aliased slice wouldn't make any sense otherwise
1587 | s_copy[i] = item.Clone()
1588 | }
1589 |
1590 | // Pass a copy of "s" to third-party function to ensure it doesn't modify it
1591 | thirdparty.Dependency(s_copy)
1592 |
1593 | // Now return a deep copy of the internal slice to prevent any mutations
1594 | internal_copy := make([] *Object, len(rec.internal))
1595 | for i, item := range rec.internal {
1596 | // Clone to avoid aliasing
1597 | // Copying an aliased slice wouldn't make any sense otherwise
1598 | internal_copy[i] = item.Clone()
1599 | }
1600 | return internal_copy
1601 | }
1602 | ```
1603 |
1604 | Now feel free to remove the comments and compare the above copy-bloated code
1605 | with the code protected by the `const` qualifier:
1606 |
1607 | ```go
1608 | /* WITH IMMUTABILITY */
1609 |
1610 | func (rec * const T) OurMethod(
1611 | s const [] * const Object,
1612 | ) const [] * const Object {
1613 | thirdparty.Dependency(s) // s is safe
1614 | return rec.internal // rec.internal is safe
1615 | }
1616 | ```
1617 |
1618 | If you don't like the rather verbose type definitions then consider using type
1619 | aliasing to shorten the code:
1620 |
1621 | ```go
1622 | type ConstSlice = const [] * const Object
1623 |
1624 | func (rec * const T) OurMethod(s ConstSlice) ConstSlice {
1625 | thirdparty.Dependency(s)
1626 | return rec.internal
1627 | }
1628 | ```
1629 |
1630 | ### 4.9. Why do we need the distinction between immutable and mutable reference types?
1631 | Simply put, the question is: *why do we have to write out the rather verbose
1632 | `const * const Object` and `const [] const Object` instead of just
1633 | `const *Object` and `const []Object` respectively?*
1634 |
1635 | There are certain situations where mutable references to immutable types are
1636 | necessary such as when we want to describe a dynamic, interlinked graph data
1637 | structure where the nodes of the graph are immutable.
1638 |
1639 | ```go
1640 | // GraphNode represents a node with outbound and inbound connections.
1641 | // Connections can be changed, but the underlying nodes will remain immutable
1642 | type GraphNode struct {
1643 | inbound * const GraphNode
1644 | outbound * const GraphNode
1645 | }
1646 | ```
1647 |
1648 | Contrary, there are other situations where we'd require immutable references to
1649 | mutable objects, such as in the case of rather complex functions taking
1650 | references to mutable graph nodes:
1651 |
1652 | ```go
1653 | // MutateGraphNode takes an immutable pointer to a mutable graph node,
1654 | // The pointer needs to be immutable so that it behaves like an
1655 | // immutable variable so we can't accidentally change it in the scope
1656 | // of the function potentially messing up the whole calculation!
1657 | func MutateGraphNode(ref const * GraphNode) {
1658 | /*...*/
1659 | ref = &GraphNode{} // Compile-time error
1660 | /*...*/
1661 | ref.outbound = &GraphNode{} // fine
1662 | ref.Mutation() // fine
1663 | }
1664 | ```
1665 |
1666 | Without this distinction, the above code wouldn't be possible and we'd have to
1667 | compromise compile-time safety by **removing immutability** to solve similar
1668 | problems. Reference types like pointers, slices, and maps are just regular types
1669 | and should be treated as such consistently without any special regulations.
1670 |
1671 | ### 4.10. Why implicitly cast mutable to immutable types?
1672 | It's true that in Go, types are converted explicitly with interface types being
1673 | the only exception to this rule. But making the typecasting of mutable- to
1674 | immutable types explicit would break backward-compatibility (see [issue
1675 | #14](https://github.com/romshark/Go-2-Proposal---Immutability/issues/14) for
1676 | more details) and also make the language rather verbose.
1677 |
1678 | ### 4.11. Can't these problems be solved by a linter?
1679 | Implementing immutable types with a linter would **not** solve the following
1680 | problems:
1681 | - **Verbosity and Confusion**: A linter would work based on *type names* and
1682 | require explicit definitions of immutable alias types (including primitive
1683 | types) with some kind of a pre- or postfix like "ImmutType**Const**", which is
1684 | way too verbose and confusing compared to the `const` qualifier based
1685 | approach! Nobody will ever write code like this:
1686 |
1687 | ```go
1688 | type ConstInt int
1689 | // ConstT is an immutable T
1690 | type ConstT T
1691 | // ConstPointerConstT is an immutable pointer to an immutable T
1692 | type ConstPointerConstT * ConstT
1693 | // ConstSliceConstT is an immutable slice of pointers to immutable Ts
1694 | type ConstSliceConstT [] * ConstT
1695 |
1696 | func (r * ConstT) Method(
1697 | a ConstPointerConstT,
1698 | v ConstSliceConstT,
1699 | ) (
1700 | rv * ConstT,
1701 | ) {
1702 | var integer ConstInt = 42
1703 | /*...*/
1704 | }
1705 | ```
1706 |
1707 | ...to achieve similar results as:
1708 | ```go
1709 | func (r * const T) Method(
1710 | a const * const T,
1711 | v const [] * const T,
1712 | ) (
1713 | rv * const T,
1714 | ) {
1715 | var integer int = 42
1716 | /*...*/
1717 | }
1718 | ```
1719 | - **Cross-Package Consistency**: A linter wouldn't protect the code from
1720 | cross-package mutability issues! If any external code like a third-party
1721 | library doesn't support the *immutable type name convention* of the linter
1722 | then the immutability simply can't be checked on these parts of the program
1723 | code making the entire concept useless. The standard library will never be
1724 | written in such a style, but it's essential to all Go 1.x code.
1725 |
1726 | ## 5. Other Proposals
1727 | ### 5.1. [proposal: spec: add read-only slices and maps as function arguments #20443](https://github.com/golang/go/issues/20443)
1728 | The proposed kind of immutability described in the document above doesn't solve
1729 | the mutable shared state problem caused by [pointer
1730 | aliasing](https://en.wikipedia.org/wiki/Pointer_aliasing) at all proposing only
1731 | exceptional treatment of slices and maps passed as function arguments.
1732 |
1733 | #### 5.1.1. Disadvantages
1734 | - **Inconsistent:** it introduces exceptional rules for map- and slice-type
1735 | arguments leading to eventual specification inconsistency.
1736 | - **Doesn't solve the root problem:** it doesn't take mutable pointer types into
1737 | account which are prone to [pointer
1738 | aliasing](https://en.wikipedia.org/wiki/Pointer_aliasing) leading to mutable
1739 | shared state just like slices and maps.
1740 | - **Very limited:** it doesn't propose immutability for variables, methods,
1741 | fields, return values and arguments of any other type than slices and maps.
1742 | - **Leads to performance degradation:** it proposes shallow-copying of slices
1743 | and maps passed to function argument instead of actual compile-time
1744 | immutability errors (even though it mentions it).
1745 | - **Unclear:** it doesn't clearly define how to handle slice aliasing.
1746 | - **Unclear:** it also doesn't clearly define how to handle nested container
1747 | types.
1748 | - **Very limited:** it won't allow different combinations of mutable and
1749 | immutable types (such as passing mutable references inside immutable slices
1750 | and similar).
1751 |
1752 | #### 5.1.2. Similarities
1753 | - Similar `const` keyword overloading with the same argumentation with slight
1754 | differences (used as argument field qualifier rather than as argument type
1755 | qualifier).
1756 |
1757 | ----
1758 |
1759 | ### 5.2. [proposal: Go 2: read-only types #22876](https://github.com/golang/go/issues/22876)
1760 | The proposed `ro` qualifier described in the document above is similar to
1761 | current proposal but still has some significant differences.
1762 |
1763 | #### 5.2.1. Disadvantages
1764 | - **Backward-incompatible:** the proposed feature requires
1765 | backward-incompatible language changes.
1766 | - **Limiting and partially pointless:** _`ro` Transitivity_ describes the
1767 | inheritance of immutability by types referenced by immutable references, which
1768 | limits the ability of the developer to describe *immutable references to
1769 | mutable objects* and similar. This limitation will make developers avoid using
1770 | `ro` types alltogether and use unsafe mutable types instead when a mix between
1771 | mutable and immutable references is necessary. An immutable slice of mutable
1772 | slices: `const [] [] int` wouldn't be possible with `ro` transitivity and
1773 | would leave the developer no choice but turning it into a mutable slice of
1774 | mutable slices: `[][]int` making the entire concept of read-only types
1775 | partially pointless.
1776 | - **Less advanced:** it doesn't propose immutable struct fields.
1777 |
1778 | #### 5.2.2. Differences
1779 | - "Immutability" is called "read-only type permissions" while constants are
1780 | called "immutables".
1781 |
1782 | #### 5.2.3. Similarities
1783 | - The proposed `ro` qualifier is part of the type definition just as the `const`
1784 | qualifier.
1785 | - Proposes immutable return values, arguments, interfaces and receivers.
1786 | - Describes very similar benefits.
1787 |
1788 | ----
1789 | Copyright © 2018 [Roman Sharkov](https://github.com/romshark)
1790 | ()
1791 |
--------------------------------------------------------------------------------
/document_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romshark/Go-1-2-Proposal---Immutability/cfc0d8eadce334f1825b046ce08728a98759a30d/document_header.png
--------------------------------------------------------------------------------