├── README.md └── document_header.png /README.md: -------------------------------------------------------------------------------- 1 | ![Immutability - A Go Language Feature Proposal](https://github.com/romshark/Go-2-Proposal---Immutability/blob/master/document_header.png "Immutability - A Go Language Feature Proposal") 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 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
Author 16 | Roman Sharkov (roman.scharkov@gmail.com) 17 |
StatusPublic (#27975)
Version1.0.1
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 --------------------------------------------------------------------------------