From b66b412c5d4a008af394fea592b815ea4d56a214 Mon Sep 17 00:00:00 2001 From: Go101 <22589241+go101@users.noreply.github.com> Date: Sat, 25 May 2019 15:27:32 +0800 Subject: [PATCH] v9.a --- README-v2.md | 6 +- README-v3.md | 11 +- README-v4.md | 7 - README-v5.md | 8 - README-v6.md | 24 +- README-v7.md | 25 +- README-v8.md | 50 +- README-v9.md => README-v9.0.md | 57 +-- README-v9.1.md | 811 ++++++++++++++++++++++++++++++++ README-v9.a.md | 517 +++++++++++++++++++++ README.md | 817 +-------------------------------- 11 files changed, 1393 insertions(+), 940 deletions(-) rename README-v9.md => README-v9.0.md (96%) create mode 100644 README-v9.1.md create mode 100644 README-v9.a.md diff --git a/README-v2.md b/README-v2.md index 4d98cde..c42ab84 100644 --- a/README-v2.md +++ b/README-v2.md @@ -1,9 +1,5 @@ # A Go immutable values proposal -Old versions: -* [the propsoal thread](https://github.com/golang/go/issues/29422). -* [the `var:N` version](README-v1.md) - The new syntax is not Go 1 compatible, please read the last section of this proposal for incompatible cases. @@ -155,7 +151,7 @@ Short value declaration examples: { newA, newB, oldC := (var.fixed)(va), vb, vc newA, newB, oldC := va.(fixed), vb, vc // equivalent to the above line - + newX, newY, oldZ := (Tx.fixed)(va), (Ty)(vb), vc newX, newY, oldZ := (Tx)(va).(fixed), (Ty)(vb), vc // equivalent to the above line } diff --git a/README-v3.md b/README-v3.md index 7f99b3d..f503351 100644 --- a/README-v3.md +++ b/README-v3.md @@ -1,10 +1,5 @@ # A Go immutable types/values proposal -Old versions: -* [the propsoal thread](https://github.com/golang/go/issues/29422). -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) - This proposal introduces a notation `T.fixed` to represent the immutable version of type `T`, where `fixed` is new introduced keyword, which makes this proposal not Go 1 compatible. Please read the last section of this proposal for incompatible cases. @@ -101,7 +96,7 @@ Those rules are much straightforward and anticipated. Please note, the immutability semantics in this proposal is different from the `const` semantics in C/C++. For example, a value declared as `var.fixed p ***int` in this proposal is like a variable decalared as `int const * const * const * p` in C/C++. -In C/C++, we can declare a variable as `int * const * const * x`, +In C/C++, we can declare a variable as `int * const * const * x`, in this proposal, no ways to declare variables with the similar immutabilities. Another example, the following C code are valid. @@ -182,7 +177,7 @@ Short value declaration examples: newA, newB, oldC := (var.fixed)(va), vb, vc newA, newB, oldC := (?.fixed)(va), vb, vc newA, newB, oldC := va.(fixed), vb, vc // equivalent to the above two lines - + newX, newY, oldZ := (Tx.fixed)(va), (Ty)(vb), vc newX, newY, oldZ := (Tx)(va).(fixed), (Ty)(vb), vc // equivalent to the above line } @@ -381,7 +376,7 @@ The immutable version of a type may have a different method set from the type. The new keyword `fixed` is one cause why this proposal is not Go 1 compatible. Assume a source file imports a package as `T` and if there is a type named `fixed` in the imported package, although a smart compiler will not mistake the `fixed` in `T.fixed` as a keyword, the `T.fixed` really hurts code readibilty. - + Using the old `const` keyword instead of the new `fixed` keyword can avoid these problems, however it would make people be confused with the current constant things. (Maybe, it is an acceptable solution.) diff --git a/README-v4.md b/README-v4.md index a096616..fd00e98 100644 --- a/README-v4.md +++ b/README-v4.md @@ -1,12 +1,5 @@ # A Go immutable types/values proposal -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422). -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) - - This proposal not Go 1 compatible. Please read the last section of this proposal for incompatible cases. Any criticisms and improvement ideas are welcome, for diff --git a/README-v5.md b/README-v5.md index f98dc50..25873a2 100644 --- a/README-v5.md +++ b/README-v5.md @@ -1,13 +1,5 @@ # A Go immutable types/values proposal -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422). -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) -* [the immutable-type/value interpretation version: const.fixed](README-v4.md) - - This proposal not Go 1 compatible. Please read the last section of this proposal for incompatible cases. Any criticisms and improvement ideas are welcome, for diff --git a/README-v6.md b/README-v6.md index b81aca6..8a03208 100644 --- a/README-v6.md +++ b/README-v6.md @@ -1,15 +1,5 @@ # A proposal to support read-only and immutable values in Go -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422) -* [the initial proposal](README-v0.md) -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) -* [the immutable-type/value interpretation version: const.fixed](README-v4.md) -* [the immutable-type/value interpretation version: final.fixed. With interface design flaw](README-v5.md) - - This proposal is not Go 1 compatible. Please read the last section of this proposal for incompatible cases. @@ -206,7 +196,7 @@ The current design may be not perfect, so any improvemnt ideas are welcome. Some examples of the full value declaration form: ```golang // A true immutable value which can't be modified in any (safe) way. -final FileNotExist = errors.New("file not exist").fixed +final FileNotExist = errors.New("file not exist").fixed // The following two declarations are equivalent. // Note, the elements of "a" are all true immutable values. @@ -334,19 +324,19 @@ func foo() { z := x[:1] // z is var.fxied value (of type []T.fixed) x = nil // ok y = T{} // ok - + final w = x // w is a final.fixed value u := w[:] // w[:] is a final.fixed value, but // u is deduced as a var.fixed value. - + // v is a final.normal value. final v = []T{{123, nil}, {789, new(int)}} v = nil // error: v is a final value v[1] = T{} // ok - + _ = append(u, T{}) // error: can't append to fixed slices _ = append(v, T{}) // ok - + ... } ``` @@ -403,7 +393,7 @@ func bar(v map[string]T.fixed) { // v is a var.fixed value *v["foo"].b = 789 // error: v["foo"].b is a fixed value v["foo"] = T{} // error: v["foo"] is a fixed map v["baz"] = T{} // error: v["foo"] is a fixed map - + // m will be deduced as a var.fixed value. // That means as long as one element or one key is fixed // in a map literal, then the map value is a fixed value. @@ -415,7 +405,7 @@ func bar(v map[string]T.fixed) { // v is a var.fixed value for a, b := range m { // a and b are both var.fixed values. // Their types are *int.fixed. - + *a = 123 // error: a is a fixed value *b = 789 // error: b is a fixed value } diff --git a/README-v7.md b/README-v7.md index a1f4aed..9a192ac 100644 --- a/README-v7.md +++ b/README-v7.md @@ -1,16 +1,5 @@ # A proposal to support read-only and immutable values in Go -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422) -* [the initial proposal](README-v0.md) -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) -* [the immutable-type/value interpretation version: const.fixed](README-v4.md) -* [the immutable-type/value interpretation version: final.fixed. With interface design flaw](README-v5.md) -* [the immutable-type/value interpretation version: final.fixed. Without partial read-only](README-v6.md) - - This proposal is not Go 1 compatible. Please read the last section of this proposal for incompatible cases. @@ -220,7 +209,7 @@ The current design may be not perfect, so any improvemnt ideas are welcome. Some examples of the full value declaration form: ```golang // A true immutable value which can't be modified in any (safe) way. -final FileNotExist = errors.New("file not exist").fixed +final FileNotExist = errors.New("file not exist").fixed // The following two declarations are equivalent. // Note, the elements of "a" are all true immutable values. @@ -340,19 +329,19 @@ func foo() { z := x[:1] // z is var.fxied value (of type []T.fixed) x = nil // ok y = T{} // ok - + final w = x // w is a final.fixed value u := w[:] // w[:] is a final.fixed value, but // u is deduced as a var.fixed value. - + // v is a final.normal value. final v = []T{{123, nil}, {789, new(int)}} v = nil // error: v is a final value v[1] = T{} // ok - + _ = append(u, T{}) // error: can't append to fixed slices _ = append(v, T{}) // ok - + ... } ``` @@ -409,7 +398,7 @@ func bar(v map[string]T.fixed) { // v is a var.fixed value *v["foo"].b = 789 // error: v["foo"].b is a fixed value v["foo"] = T{} // error: v["foo"] is a fixed map v["baz"] = T{} // error: v["foo"] is a fixed map - + // m will be deduced as a var.fixed value. // That means as long as one element or one key is fixed // in a map literal, then the map value is a fixed value. @@ -421,7 +410,7 @@ func bar(v map[string]T.fixed) { // v is a var.fixed value for a, b := range m { // a and b are both var.fixed values. // Their types are *int.fixed. - + *a = 123 // error: a is a fixed value *b = 789 // error: b is a fixed value } diff --git a/README-v8.md b/README-v8.md index 58c83ba..24e3f08 100644 --- a/README-v8.md +++ b/README-v8.md @@ -1,16 +1,4 @@ -# A proposal to support read-only and immutable values in Go - -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422), and the [golang-dev thread](https://groups.google.com/forum/#!topic/golang-dev/5M9F09S_k0g) -* [the initial proposal](README-v0.md) -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) -* [the immutable-type/value interpretation version: const+fixed](README-v4.md) -* [the immutable-type/value interpretation version: final+fixed. With interface design flaw](README-v5.md) -* [final+fixed. Without partial read-only](README-v6.md) -* [final+fixed. With partial read-only](README-v7.md) -* const + reader/writer roles. Partial read-only removed. (v8, the currrent version) +# A proposal to support read-only and practical immutable values in Go _(Comparing to the last revision v7, this revision removes partial read-only, for partial read-only will bring many complexities and cause some design flaws.)_ @@ -114,9 +102,9 @@ local variable/constants, and function parameter/result variables. But it can't be used to specifiy struct field types. Fields of a struct value will inherit the roles from the struct value. -There is not the `T:writer` notation. +There is not the `T:writer` notation. The *writer role* concept does exist. -The raw `T` notation means a writer type (in non-struct-field declarations). +The raw `T` notation means a writer type (in non-struct-field declarations). Thw word `reader` can be either a keyword or not. If it is designed as not, then it can be viewed as a predeclared role. @@ -187,23 +175,23 @@ An example: const y = x // y is a writer constant var z []*int:reader = x // z is a reader variable const w = y:reader // w is a reader constant - + // x, y, z and w share elements. - + x[0] = new(int); *x[0] = 123 // ok x = nil // ok println(*z[0]) // 123 - + y[0] = new(int); *y[0] = 789 // ok y = nil // error: y is a constant println(*w[0]) // 789 - + *z[0] = 555; z[0] = new(int) // error: z[0] and *z[0] are read-only z = nil // ok - + *w[0] = 555; w[0] = new(int) // error: w[0] and *w[0] are read-only w = nil // error: w is a constant - + x = z // error: reader value z can't be assigned to writer value x } ``` @@ -214,7 +202,7 @@ However, in the following example, the slice elements are immutable. { var s = []int{1, 2, 3}:reader s[0] = 9 // error: s[0] is read-only - + // S and its elements are both immutable. const S = []int{1, 2, 3}:reader } @@ -280,7 +268,7 @@ func foo() { w := x // w is deduced as a writer variable of type []int. z[0] = 9 // error: z[0] is read-only. u := &z // u is like y. - + p1 := &x[1] // p1 is a writer pointer variable. p2 := &z[1] // p2 is a reader pointer variable. ... @@ -343,18 +331,18 @@ func foo() { z := x[:1] // liek x, z is reader slice. x = nil // ok y = T{} // ok - + const w = x // w is a reader constant. u := w[:] // u is a reader slice variable. - + // v is a writer slice constant. const v = []T{{123, nil}, {789, new(int)}} v = nil // error: v is a constant v[1] = T{} // ok - + _ = append(u, T{}) // error: can't append to reader slices _ = append(v, T{}) // ok - + ... } ``` @@ -420,7 +408,7 @@ func bar(v map[string]T:reader) { // v is a reader variable *v["foo"].b = 789 // error: v["foo"].b is read-only v["foo"] = T{} // error: v is a reader map v["baz"] = T{} // error: v is a reader map - + // m will be deduced as a reader map variable. // That means as long as one element or one key is a reader // value in a map literal, then the map is also a reader value. @@ -431,7 +419,7 @@ func bar(v map[string]T:reader) { // v is a reader variable } for a, b := range m { // a and b are both reader values of type *int:reader. - + *a = 123 // error: *a is read-only *b = 789 // error: *b is read-only } @@ -503,7 +491,7 @@ Use the `Split` function. var x = []byte{"aaa/bbb/ccc/ddd"} _ = Split(x, []byte("/")) // call the writer version _ = Split(x:reader, []byte("/")) // call the reader version - + // Use Split function as values. var fw = Split{r: writer} // I haven't got better syntax yet. var fr = Split{r: reader} @@ -803,6 +791,6 @@ To support partial read-only, the following rules need to be added: Another simpler rule design is to forbid the conversions mentioned in the 2nd and 3rd rules. -This means, the addressable constant feature and the partial read-only feature are mutually exclusive. +This means, the addressable constant feature and the partial read-only feature are mutually exclusive. diff --git a/README-v9.md b/README-v9.0.md similarity index 96% rename from README-v9.md rename to README-v9.0.md index a44f2fc..0965ec2 100644 --- a/README-v9.md +++ b/README-v9.0.md @@ -1,17 +1,4 @@ -# A proposal to support read-only and immutable values in Go - -Old versions: -* [the proposal thread](https://github.com/golang/go/issues/29422), and the [golang-dev thread](https://groups.google.com/forum/#!topic/golang-dev/5M9F09S_k0g) -* [the initial proposal](README-v0.md) -* [the `var:N` version](README-v1.md) -* [the pure-immutable-value interpretation version](README-v2.md) -* [the immutable-type interpretation version](README-v3.md) -* [the immutable-type/value interpretation version: const+fixed](README-v4.md) -* [the immutable-type/value interpretation version: final+fixed. With interface design flaw](README-v5.md) -* [final+fixed. Without partial read-only](README-v6.md) -* [final+fixed. With partial read-only](README-v7.md) -* [const+reader/writer roles. Partial read-only removed](README-v8.md) -* final+reader/writer roles. (v9, the currrent version) +# A proposal to support read-only and practical immutable values in Go _(This revision reverts the `const` keyword in v8 to `final`, to avoid some confusions.)_ @@ -114,9 +101,9 @@ local variables/finals, and function parameter/result variables. But it can't be used to specifiy struct field types. Fields of a struct value will inherit the roles from the struct value. -There is not the `T:writer` notation. +There is not the `T:writer` notation. The *writer role* concept does exist. -The raw `T` notation means a writer type (in non-struct-field declarations). +The raw `T` notation means a writer type (in non-struct-field declarations). Thw word `reader` can be either a keyword or not. If it is designed as not, then it can be viewed as a predeclared role. @@ -187,23 +174,23 @@ An example: final y = x // y is a writer final var z []*int:reader = x // z is a reader variable final w = y:reader // w is a reader final - + // x, y, z and w share elements. - + x[0] = new(int); *x[0] = 123 // ok x = nil // ok println(*z[0]) // 123 - + y[0] = new(int); *y[0] = 789 // ok y = nil // error: y is a final println(*w[0]) // 789 - + *z[0] = 555; z[0] = new(int) // error: z[0] and *z[0] are read-only z = nil // ok - + *w[0] = 555; w[0] = new(int) // error: w[0] and *w[0] are read-only w = nil // error: w is a final - + x = z // error: reader value z can't be assigned to writer value x } ``` @@ -214,7 +201,7 @@ However, in the following example, the slice elements are immutable. { var s = []int{1, 2, 3}:reader s[0] = 9 // error: s[0] is read-only - + // S and its elements are both immutable. final S = []int{1, 2, 3}:reader } @@ -280,7 +267,7 @@ func foo() { w := x // w is deduced as a writer variable of type []int. z[0] = 9 // error: z[0] is read-only. u := &z // u is like y. - + p1 := &x[1] // p1 is a writer pointer variable. p2 := &z[1] // p2 is a reader pointer variable. ... @@ -343,18 +330,18 @@ func foo() { z := x[:1] // liek x, z is reader slice. x = nil // ok y = T{} // ok - + final w = x // w is a reader final. u := w[:] // u is a reader slice variable. - + // v is a writer slice final. final v = []T{{123, nil}, {789, new(int)}} v = nil // error: v is a final v[1] = T{} // ok - + _ = append(u, T{}) // error: can't append to reader slices _ = append(v, T{}) // ok - + ... } ``` @@ -420,7 +407,7 @@ func bar(v map[string]T:reader) { // v is a reader variable *v["foo"].b = 789 // error: v["foo"].b is read-only v["foo"] = T{} // error: v is a reader map v["baz"] = T{} // error: v is a reader map - + // m will be deduced as a reader map variable. // That means as long as one element or one key is a reader // value in a map literal, then the map is also a reader value. @@ -431,7 +418,7 @@ func bar(v map[string]T:reader) { // v is a reader variable } for a, b := range m { // a and b are both reader values of type *int:reader. - + *a = 123 // error: *a is read-only *b = 789 // error: *b is read-only } @@ -503,7 +490,7 @@ Use the `Split` function. var x = []byte{"aaa/bbb/ccc/ddd"} _ = Split(x, []byte("/")) // call the writer version _ = Split(x:reader, []byte("/")) // call the reader version - + // Use Split function as values. var fw = Split{r: writer} // I haven't got better syntax yet. var fr = Split{r: reader} @@ -544,6 +531,8 @@ the method set of type `T:reader` is a subset of type `*T:reader`. (Or in other words, the method set of type `T` is a subset of type `*T` if type `T` is not an interface type.) +Role parameters don't work for receiver parameters. + #### interfaces An interface type can specify some read-only methods. For example: @@ -604,8 +593,6 @@ then the reader type `T:reader` also implements the reader interface type `I:rea For this reason, the `xyz ...interface{}` parameter declarations of all the print functions in the `fmt` standard package should be changed to `xyz ...interface{}:reader` instead. -Role parameters don't work for receiver parameters. - Example: ``` var x = []int{1, 2, 3} @@ -844,7 +831,7 @@ import "x.y,z/apkg" fnc main() { ... - + s := []byte{1, 2, 3} apkg.F(s) ... @@ -859,7 +846,7 @@ import "x.y,z/apkg" fnc main() { ... - + s := []byte{1, 2, 3} apkg.F(s:reader) ... diff --git a/README-v9.1.md b/README-v9.1.md new file mode 100644 index 0000000..e8e3638 --- /dev/null +++ b/README-v9.1.md @@ -0,0 +1,811 @@ +# A proposal to support read-only and practical immutable values in Go + +_Comparing to the last revision v9, this version forbids passing writer values as reader arguments (including receiver arguments), +to avoid [the problem mentioned in v9](README-v9.md#the-problem-when-reader-parameters-in-a-library-package-changed-to-writers)._ + +This revision is a little Go 1 incompatible, for it needs a new keyword `final`. + +Any criticisms and improvement ideas are welcome, for +* I have not much compiler-related knowledge, so the following designs may have flaws. +* I haven't found a perfect syntax notation set for this proposal yet. + +## The problems this proposal tries to solve + +The problems this proposal tries to solve: +1. no ways to declare package-level exported immutable non-basic values. +1. no ways to declare read-only function parameters and results. +1. many inefficiencies caused by lacking of immutable and read-only values. + +Detailed rationales are listed in [at the end of this proposal](#rationales). +Some solutions for the drawbacks mentioned by Russ are also listed there. + +Basically, this proposal can be viewed as a combination +of [issue#6386](https://github.com/golang/go/issues/6386) +and [issue#22876](https://github.com/golang/go/issues/22876). + +This proposal also has some similar ideas with +[evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A) +written by Russ. + +However, this proposal has involved so much that +it has become into a much more practical solution with +more ideas and details than the just mentioned ones. + +This propsoal is not intended to be accepted, at least soon. +It is more a documentation to show the problems and possible solutions +in supporting immutable and read-only values in Go. + +## The main points of this proposal + +We know each value has a property, `self_modifiable`, +which means whether or not that value itself is modifiable. + +This proposal will add a new value property `ref_modifiable` for each value. +This property means whether or not the values referenced +(either directly or indirectly) by a value are modifiable. +The `ref_modifiable` property will be called a value role later. + +The permutation of thw two properties results 4 genres of values: +1. `{self_modifiable: true, ref_modifiable: true}`. + Such as variables. +1. `{self_modifiable: true, ref_modifiable: false}`. + No such Go values currently. +1. `{self_modifiable: false, ref_modifiable: true}`. + Such as composite literals. + (In fact, all constants in JavaScript and + all final variables decalred in Java belong to this genre.) +1. `{self_modifiable: false, ref_modifiable: false}`. + No such Go values currently. + +(Note, in fact, we can catagory declared function values, method values +and constant basic values into either the 3rd or the 4th genre.) + +This proposal will let Go support values of the 2nd and 4th genres, +and extend the value range of the 3rd genre. + +#### variables and finals + +This proposal treats the `self_modifiable` as a value property. +* `{self_modifiable: true}` values (variables) are declared with `var`. +* `{self_modifiable: false}` values (finals) are declared with `final` + Like named constants, a named final must be bound a value in its declaration. + But unlike named constants, finals can be values of any type, not limited to values of basic types. + We can view finals as runtime constants. + Please note that, although a final itself can't be modified, + the values referenced by the final might be modifiable. + (Much like JavaScript `const` values and Java `final` values.) + +Most intermediate results in Go should be viewed as final values, +including function returns, operator operation evaluation results, +explicit value conversion results, etc. + +Finals (themselves) are immutable values. +Note, although a final itself is an immutable value, +whether or not the values referenced by the final are immutable +values depends on the specified role (see the next section) of the final. + +There is not a short final declartion form. +Shorted declared values are all variables. +Function parameters and results also can't be delcared as finals. + +#### value roles: reader and writer + +This proposal proposes to add a value role concept to +denote the `ref_modifiable` property. +Although roles are properties of values, +to ease the syntax designs, we can think they are also properties of types. + +The notation `T:reader` is introduced to represent a reader type. +Its values are called reader values. +The notation can be used to declare package-level variables/finals, +local variables/finals, and function parameter/result variables. +But it can't be used to specifiy struct field types. +Fields of a struct value will inherit the roles from the struct value. + +There is not the `T:writer` notation. +The *writer role* concept does exist. +The raw `T` notation means a writer type (in non-struct-field declarations). + +Thw word `reader` can be either a keyword or not. +If it is designed as not, then it can be viewed as a predeclared role. + +The meanings of **reader** and **writer** values: +* All values referecned by a reader value are read-only (and also reader) values + (from the view of the reader value). + In other words, a reader value represents a read-only value chain, + and the reader value is the head of the chain. + Note, the reader head itself might be a variable, which is not a read-only value. +* All values referecned by a writer value are writable (and also writer) values + (from the view of the writer value). + In other words, a writer value represents a writable value chain, + and the writer value is the head of the chain. + Note, the writer head itself might be a final, which is a read-only value. + +Some details about the `T:reader` notation need to be noted: +1. `:reader` is not allowed to appear in type declarations, + except it shows up as function parameter and result roles. + * For example, `type T []int:reader` is invalid, + but `type T func (T:reader)` is valid. + * And please note that, as long as one result of a function is a reader value, + then the result list part of the function proptotype literal + must be enclosed in a pair of `()`. For example, + `func() T:reader` is invalid, it must be `func() (T:reader)`. +1. The notation `[]*chan T:reader` can only mean `([]*chan T):reader`, + whereas `[]*chan (T:reader)`, `[]*((chan T):reader)` + and `[]((*chan T):reader)` are all invalid notations. +1. Some kinds of types are always non-reader types, including basic types and function types. + So, `:reader` is not allowed to follow such type names and literal. + For example (again), `func() T:reader` is invalid. + The saying of "the reader version of a non-reader type" does exist. + The reader version of a non-reader type is the non-reader type itself. +1. Struct types which all field types are non-reader types + and array/channel types with non-reader element types + are also non-reader types. But, to avoid const-poisoning alike problems, + such type notations can be followed with `:reader`. + But the `:reader` suffix is a non-sense for such types. + For example, `[5]struct{a int}` and `[5]struct{a int}:reader` + have no differences. + +A notation `v:reader` is introduced to convert a writer value `v` to a reader value, +The `:reader` suffix can only follow r-values (right-hand-side values). + +You may have got it, a value hosted at a specified memory address may +represent as a read-only value or a writable value, depending on context. +So a non-final read-only values might be not an immutable value. +(But there are really some non-final read-only values which are immutable values. +Please read the following for such examples.) + +#### assignment and conversion rules + +Above has mentioned: +* a named final must be bound to a value in its declaration. + It can't be assigned to again later. +* a writer value is assignable to a reader variable, but + a writer value can't be passed as a reader argument, it must be + explicitly converted to a reader value to be used as a reader argument. +* a reader value is not assignable to a writer variable. +* a writer value can be converted to a reader value, but not vice versa. + +#### some examples + +An example: +``` +{ + var x = []*int{new(int)} // x is a writer variable + final y = x // y is a writer final + var z []*int:reader = x // z is a reader variable + final w = y:reader // w is a reader final + + // x, y, z and w share elements. + + x[0] = new(int); *x[0] = 123 // ok + x = nil // ok + println(*z[0]) // 123 + + y[0] = new(int); *y[0] = 789 // ok + y = nil // error: y is a final + println(*w[0]) // 789 + + *z[0] = 555; z[0] = new(int) // error: z[0] and *z[0] are read-only + z = nil // ok + + *w[0] = 555; w[0] = new(int) // error: w[0] and *w[0] are read-only + w = nil // error: w is a final + + x = z // error: reader value z can't be assigned to writer value x +} +``` + +In the above, the elements of the slices are not immutable values. +However, in the following example, the slice elements are immutable. +``` +{ + var s = []int{1, 2, 3}:reader + s[0] = 9 // error: s[0] is read-only + + // S and its elements are both immutable. + final S = []int{1, 2, 3}:reader +} +``` + +More examples: +``` +// An immutable error value. +final FileNotExist = errors.New("file not exist"):reader + +var n int:reader // error: int is a non-reader type + +// Two functions with read-only parameters and results. +// All parameters are varibles. +// Note that "chan int" is a non-reader type. +func Foo(m http.Request:reader, n map[string]int:reader) (o []int:reader, p chan int) {...} +func Print(values ...interface{}:reader) {...} + +// Some short declartions. The items on the left sides +// are all variables. No ways to short delcare finals. +{ + oldA, newB := va, vb:reader // newB is a reader variable + + // Explicit conversions. + // The four lines are equivalent to each other. + newX, oldY := (Tx:reader)(va), vy + newX, oldY := (Tx(va)):reader, vy + newX, oldY := Tx(va:reader), vy + newX, oldY := Tx(va):reader, vy +} +``` + +#### about final, reader, read-only, and immutable values + +From the above descriptions and explanations, we know: +* a final itself is not only a read-only value, it is also an immutable value. +* a reader value may be a variable or a final, + so it may be read-only or writable. +* a writer value may be a variable or a final, + so it may be read-only or writable. +* some read-only values are immutable values, but most are not. + +No data synchronizations are needed in concurrently reading immutable values, +but data synchronizations are still needed when concurrently reading +a read-only value which is not an immutable value. + +NOTE: the above mentioned "immutable values" all means "practically immutable values". +Such values may be modified through unsafe ways. + +## Detailed rules for values of all kinds of types + +#### safe pointers + +* Dereference of a reader pointer results a read-only value. +* Dereference of a writer pointer results a writable value. +* Taking address of an addressable final or a reader value results a reader pointer. +* Taking address of an addressable writer value results a writer pointer. + +Example: +``` +final x = []int{1, 2, 3} + +func foo() { + y := &x // y is reader pointer variable of type *[]int:reader. + z := *y // z is deduced as a reader variable of type []int:reader. + w := x // w is deduced as a writer variable of type []int. + z[0] = 9 // error: z[0] is read-only. + u := &z // u is like y. + + p1 := &x[1] // p1 is a writer pointer variable. + p2 := &z[1] // p2 is a reader pointer variable. + ... +} +``` + +#### unsafe pointers + +* Reader pointers and writer pointers can be both converted to unsafe pointers. + This means the read-only rules built by this proposal can be broken by the unsafe mechanism. + (This is important for reflection implementation.) + +Example: +``` +func mut(x []int:reader) []int { + return *((*[]int)(unsafe.Pointer(&x))) +} +``` +#### structs + +* Fields of reader struct values are also reader values. +* Fields of writer struct values are also writer values. +* Fields of read-only struct values are also read-only values. +* Fields of writable struct values are also writable values. + +#### arrays + +* Elements of reader array values are also reader values. +* Elements of writer array values are also writer values. +* Elements of read-only array values are also read-only values. +* Elements of writable array values are also writable values. + +#### slices + +* Elements of reader slice values are read-only and reader values. +* Elements of writer slice values are writable and writer values. +* We can't append elements to reader slice values. +* Subslice: + * The subslice result of a reader slice is still a reader slice. + * The subslice result of a writer slice is still a writer slice. + * The subslice result of a final or reader array is a reader slice. + +Example 1: +``` +type T struct { + a int + b *int +} + +// The type of x is []T:reader. +var x = []T{{123, nil}, {789, new(int)}}:reader + +func foo() { + x[0] = nil // error: x[0] is read-only. + x[0].a = 567 // error: x[0] is read-only. + y := x[0] // y is a reader value of type T:reader. + y.a = 567 // ok + *y.b = 567 // error: y.b is read-only + y.b = nil // ok + z := x[:1] // liek x, z is reader slice. + x = nil // ok + y = T{} // ok + + final w = x // w is a reader final. + u := w[:] // u is a reader slice variable. + + // v is a writer slice final. + final v = []T{{123, nil}, {789, new(int)}} + v = nil // error: v is a final + v[1] = T{} // ok + + _ = append(u, T{}) // error: can't append to reader slices + _ = append(v, T{}) // ok + + ... +} +``` + +Example 2: +``` +var x = []int{1, 2, 3} + +// External packages have no ways to modify elements of x (through S). +final S = x:reader + +// The elements of R can't even be modified in current package! +// It and its elements are all immutable values. +final R = []int{7, 8, 9}:reader + +// Q itself can't be modified, but its elements can. +final Q = []int{7, 8, 9} +``` + +Example 3: +``` +var s = "hello word" + +// "bs" is a reader byte slice. +// A clever compiler will not allocate a +// duplicate underlying byte sequence here. +var bs = []byte:reader(s) // <=> []byte(s):reader +{ + pw := &s[6] // pw is reader poiner whose base type is "byte". + pw = &bs[6] // ok +} +``` + +Note, internally, the `cap` field of a reader byte slice is set to `-1` +if the byte slice is converted from a string, so that Go runtime knows +its elements are immutable. Converting such a reader byte slice to +a string doesn't need to duplicate the underlying bytes. + +#### maps + +* Elements of reader map values are read-only and reader values. +* Elements of writer map values are writable and writer values. + (Each writable map element must be modified as a whole.) +* Keys (exposed in for-range) of reader map values are reader values. +* Keys (exposed in for-range) of writer map values are writer values. +* We can't append new entries to (or replace entries of, + or delete old entries from) reader map values. + +Example: +``` +type T struct { + a int + b *int +} + +// x and its entries are all immutable values. +final x = map[string]T{"foo": T{a: 123, b: new(int)}}:reader + +bar(x) // ok + +func bar(v map[string]T:reader) { // v is a reader variable + // Both v["foo"] and v["foo"].b are both reader values. + *v["foo"].b = 789 // error: v["foo"].b is read-only + v["foo"] = T{} // error: v is a reader map + v["baz"] = T{} // error: v is a reader map + + // m will be deduced as a reader map variable. + // That means as long as one element or one key is a reader + // value in a map literal, then the map is also a reader value. + m := map[*int]*int { + new(int): new(int):reader, + new(int): new(int), + new(int): new(int), + } + for a, b := range m { + // a and b are both reader values of type *int:reader. + + *a = 123 // error: *a is read-only + *b = 789 // error: *b is read-only + } +} +``` + +#### channels + +* Send + * We can't send values to final channels. + * We can send values of any genres to a reader channel. + * We can only send writer values to a writer channel. +* Receive + * We can't receive values from final channels. + * Receiving from a reader channel results a reader value. + * Receiving from a writer channel results a writer value. + +Example: +``` +final ch = make(chain *int, 1) + +func foo(c chan *int:reader) { + x := <-c // ok. x is a reader variable of type *int:reader. + y := new(int) + c <- y // ok + + ch <- x // error: ch is a final channel + <-ch // error: ch is a final channel + ... +} +``` + +#### functions + +Function parameters and results can be declared as reader variables. + +In the following function proptotype, parameter `x` and result `w` are declared as reader variables. +``` +func fa(x Tx:reader, y Ty) (z Tz, w Tw:reader) {...} +``` + +A `func()(T)` value is assignable to a `func()(T:reader)` value, not vice versa. + +A `func(T:reader)` value is assignable to a `func(T)` value, not vice versa. + +To avoid function duplications like the following code shows: +``` +// split writer byte slices +func Split_1(v []byte, sep []byte:reader) [][]byte {...} + +// split reader byte slices +func Split_2(v []byte:reader, sep []byte:reader) ([][]byte:reader) {...} +``` + +A role parameter concept is introduced, +so that the above two function can be declared as one: +``` +func Split(v []byte::q, sep []byte:reader) ([][]byte::q) {...} +``` + +Here, `:q` is called a role parameter. +Its name can be arbitrary non-blank identifier, +but the two occurrences must be consistent. +Short role parameter names are recommended, such as `p` and `q`. + +Use the `Split` function. +``` +{ + var x = []byte{"aaa/bbb/ccc/ddd"} + var y = Split(x, []byte("/")) // call the writer version + // y is a writer value. + var z = Split(x:reader, []byte("/")) // call the reader version + // z is a reader value. + + // Use Split function as values. + var fw = Split{q: writer} // I haven't got better syntax idea yet. + var fr = Split{q: reader} + _ = fr(x:reader, []byte("/")) +} +``` + +#### method sets + +We can delcare methods with recievers of reader types. + +The method set of reader type `T:reader` is always a subset of writer type `T`. +This means when a method `M` is explicitly declared for reader type `T:reader`, +then a corresponding implicit method with the same name +will be declared for writer type `T` by compilers. +``` +func (T:reader) M() {} // explicitly declared. (A reader method) +/* +func (t T) reader.M() {t:reader.M()} // implicitly declared. (A writer method) +*/ +/* +func T:reader.M(t T:reader) {t.M()} // an implicitly declared function. +*/ + +var t T +t:reader.M() +// <=> +T:reader.M(t:reader) +``` + +Note, to avoid [the problem mentioned in v9](README-v9.md#the-problem-when-reader-parameters-in-a-library-package-changed-to-writers), the following method/function calls are invalid: +``` +var t T +t.M() +// <=> +T:reader.M(t) +// <=> +T.M(t) +``` + +In the above code snippet, the method set of reader type `T:reader` contains one method: `reader.M`, +however the method set of type `T` contains two method: `reader.M` and `M`. + +For type `T` and `*T`, if methods can be declared for them (either explicitly or implicitly), +the method set of type `T:reader` is a subset of type `*T:reader`. +(Or in other words, the method set of type `T` is a subset of type `*T` +if type `T` is not an interface type.) + +#### interfaces + +An interface type can specify some read-only methods. For example: +``` +type I interface { + M0(Ta) Tb // a writer method + + reader.M2(Tx) Ty // a reader method. + // NOTE: this is an exported method. +} +``` + +Similar to non-interface type, if a reader interface type +explicitly specified a reader method `reader.M`, +it also implicitly specifies a writer method with the same name `M`. + +The method set specified by type `I` contains three methods, `M0`, `reader.M2` and `M2`. +The method set specified by type `I:reader` only contains one method, `reader.M2`. + +When a method is declared for a concrete type to implement a reader method, +the type of the receiver of the declared method must be a reader type. +For example, in the following code snippet, +the type `T1` implements the interface `I` shown in the above code snippet, +but the type `T2` doesn't. +``` +type T1 struct{} +func (T1) M0(Ta) Tb {var b Tb; return b} +func (T1:reader) M2(Tx) Ty {var y Ty; return y} // the receiver type is a reader type. + +type T2 struct{} +func (T2) M0(Ta) Tb {var b Tb; return b} +func (T2) M2(Tx) Ty {var y Ty; return y} // the receiver type is a writer type. +``` + +Please note, the type `T3` in the following code snippet also implements `I`. +Please read the above function section for reasons. +``` +type T3 struct{} +func (T3) M0(Ta:reader) Tb {var b Tb; return b} +func (T3:reader) M2(Tx:reader) Ty {var y Ty; return y} +``` + +If a writer type `T` implements a writer interface type `I`, +then the reader type `T:reader` also implements the reader interface type `I:reader` for sure. + +* Dynamic type + * The dynamic type of a writer interface value is a writer type. + * The dynamic type of a reader interface value is a reader type. +* Box + * No values can be boxed into final interface values (except the initial bound values). + * reader values can't be boxed into writer interface values. + * Values of any genres can be boxed into a reader interface value. +* Assert + * A type assertion on a reader interface value results a reader value. + For such an assertion, its syntax form `x.(T:reader)` can be simplified as `x.(T)`. + * A type assertion on a writer interface value results a writer value. + +For this reason, the `xyz ...interface{}` parameter declarations of all the print functions +in the `fmt` standard package should be changed to `xyz ...interface{}:reader` instead. + +Role parameters don't work for receiver parameters. + +Example: +``` +var x = []int{1, 2, 3} +var y = [][]int{x, x}:reader +var u interface{} = x // ok +u = y // error: can't assign a reader value to a writer value. +var v interface{}:reader = y // ok. v is deduced as a reader interface value. +var w = v.([][]int) // ok. Like y, w is a reader value of type [][]int:reader. +v = x // ok +var s = v.([]int) // ok, u is a reader value of type []int:reader. +var t = v.([]int:reader) // ok, equivalent to the above one. +var q = u.([]int:reader) // ok, Assert + convert. +var r = u.([]int):reader // ok, Assert then convert. Equivalent to the above one. +``` + +Another eample: +``` +type T0 []int +func (T0) M([]int) []int + +type T1 []int +func (T1) M([]int:reader) []int + +type T2 []int +func (T2) M([]int) ([]int:reader) + +type T3 []int +func (T3) M([]int:reader) ([]int:reader) + +type I interface { + M([]int) []int:reader +} + +// T0, T1, T2, and T3 all implement I. +var _ I = T0{} +var _ I = T1{} +var _ I = T2{} +var _ I = T3{} +``` + +#### reflection + +Many function and method implementations in the `refect` package should be modified accordingly. +The `refect.Value` type shoud have a **_reader_** property, +and the result of an `Elem` method call should inherit the **reader** property +from the receiver argument. More about reflection. +For all details on reflection, please read the following reflection section. + +The current `reflect.Value.CanSet` method will report whether or not a value can be modified. + +A `reflect.ReaderValueOf` function is needed to create +`reflect.Value` values representing reader Go values. +Its prototype is +``` +func ReaderValueOf(i interface{}:reader) Value +``` +For the standard Go compiler, in implementaion, +one bit should be borrowed from the 23+ bits method number +to represent the `reader` proeprty. + +All parameters of type `reflect.Value` of the functions and methods +in the`reflect` package, including receiver parameters, +should be declared as reader variables. +However, the `reflect.Value` return results should be declared as writers. + +A `reflect.Value.ToReader` method is needed to +make a `reflect.Value` value represent a reader Go value. + +A `reflect.Value.ReaderInterface` method is needed, +it returns a reader interface value. +The old `Interface` method panics on reader values. + +A method `reflect.Type.Reader` is needed to get the reader version of a writer type. +A method `reflect.Type.Writer` is needed to get the writer version of a reader type. +The method sets of reader type `T:reader` is the subset of the writer type `T`. +Their respective other properties should be identical. + +A method `reflect.Type.Genre` is needed, +it may return `Reader` or `Writer` (two constants). + + +## Compiler implementation + +I'm not familiar with the compiler development things. +It is just my feeling, by my experience, +that most of the rules mentioned in this proposal +can be enforced by compiler without big technology obstacles. + +At compile phase, compiler should maintain two bits for each value. +One bit means whether or not the value itself can be modified. +The other bit means whether or not the values referenced by the value can be modified. + +## Runtime implementation + +Except the changes mentioned in the above reflection section, +the impact on runtime made by this proposal is not large. + +Each internal method value should maintain a `reader` property. +This information is useful when boxing a reader value into an interface. + +As above has mentioned, the cap field of a reader byte slice should +be set to `-1` if its byte elements are immutable. + +## Rationales + +#### Rationales of Go needs read-only and immutable values + +In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), +Russ mentions some inefficiencies caused by lacking of read-only values. +1. Now, the type of the only parameter of `io.Writer.Write` method is `[]byte`. + In fact, it should be a read-only parameter, for two reasons: + 1. so that a `Write` call can take a string value argument without making + a duplicate underlying bytes of the string argument. (More efficient) + 1. the current method prototype doesn't prevent a custom `Writer` + from modifying the elements of the passed `[]byte` argument. (More secure) +1. By specifying a parameter of a function as read only, users of the function + will clearly know the corresponding passed arguments will not be modified + in this funciton. (Better code as document) + +Besides these improvements, immutable values (this proposal supports) +can raise the security of Go code. +For example, by changing many exported `error` values in standard packages +to immutable values, the securities of Go programs are improved. + +Immutable slice values can also let compilers to do more +BCE (bounds check elimination) optimizations for them. + +The "Strengths of This Proposal" section in @jba's [propsoal](https://github.com/golang/go/issues/22876) +also makes a good summary of the benefits of read-only values. + +#### Rationales for the `T:reader` notation + +1. It is less discrete than `reader T`. I think `func (Ta:reader) (Tx:reader)` has a better readibility than `func (reader Ta)(reader Tx)`. +1. It conforms to Go type literal design philosophy: more importants shown firstly. +1. It saves one keyword. + +#### About the problems of read-only values mentioned by Russ + +In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), +Russ mentions some problems of read-only values. +This proposal provides solutions to avoid each of these drawbacks. + +1. **function duplication** + +Solved by role parameter. + +Please see the end of [the function section](#functions). + +2. **immutability and memory allocation** + +Solved by setting the capacities of immutable byte slices as `-1` at run time. + +Please see the end of [the slice section](#slices). + +3. **loss of generality** + +Solved by letting `interface {M(T:reader)}` implement `interface {M(T)}` +and letting `interface {M() T}` implement `interface {M() T:reader}`. + +Please see [the interface section](#interfaces) for details. + +#### About partial read-only + +Sometimes, people may need partial read-only for struct values. +[An older revision](README-v7.md#structs) of this proposal supports this feature, +but it is removed from the currrent revision for it brings many complexisites +and may cause some design flaws. + +``` +type Counter struct { + mu sync.Mutex:writable // will be always writable (and also a writer), + // even if its containing struct value is a read-only. + n uint64 +} + +{ + final c Counter + c.mu.Lock() // error: c.mu is read-only + + var p = &c // p is a reader pointer of type *Counter:reader + p.mu.Lock() // ok by the rule, but it makes c become a non-immutable value, + // which may cause some confusions. + // To avoid such cases happening, we can forbid taking addresses + // of finals, but I feel this trade-off isn't worth it. +} +``` + +To support partial read-only, the following rules need to be added: +1. finals are always unaddressable. +1. values of `struct{t T:writable}` can be converted/assignable to `struct{t T}`. +1. function values + 1. values of `func (struct{t T})` can be converted/assignable to `func (struct{t T:writable})`. + 1. values of `func () struct{t T:writable}` can be converted/assignable to `func () struct{t T}`. + 1. values of `func (struct{t T})` and `func (struct{t T:writable}):reader` can't be converted to each other. + +Another simpler rule design is to forbid the conversions mentioned in the 2nd and 3rd rules. + +This means, the addressable final feature and the partial read-only feature are mutually exclusive. +I prefer keeping the addressable final feature. +This feature will break less user code, +for some user code may take the addresses of many error values declared in std packages. +This feature will continue to make such code valid. diff --git a/README-v9.a.md b/README-v9.a.md new file mode 100644 index 0000000..22020bb --- /dev/null +++ b/README-v9.a.md @@ -0,0 +1,517 @@ +# A proposal to support read-only and practical immutable values in Go + +Comparing to the v9.1 revison, this revision (v9.a) +* removes the **final value** concept, thus this branch doesn't need the `final` keyword and becomes Go 1 compatible. +* removes the restriction that sending to and receiving from read-only channels are disallowed. + +Any criticisms and improvement ideas are welcome, for +* I have not much compiler-related knowledge, so the following designs may have flaws. +* I haven't found a perfect syntax notation set for this proposal yet. + +## The problems this proposal tries to solve + +The problems this proposal tries to solve: +1. no ways to declare package-level exported immutable non-basic values. +1. no ways to declare read-only function parameters and results. +1. many inefficiencies caused by lacking of immutable and read-only values. + +Detailed rationales are listed in [at the end of this proposal](#rationales). +Some solutions for the drawbacks mentioned by Russ are also listed there. + +Basically, this proposal can be viewed as a combination +of [issue#6386](https://github.com/golang/go/issues/6386) +and [issue#22876](https://github.com/golang/go/issues/22876). + +This proposal also has some similar ideas with +[evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A) +written by Russ. + +However, this proposal has involved so much that +it has become into a much more practical solution with +more ideas and details than the just mentioned ones. + +This propsoal is not intended to be accepted, at least soon. +It is more a documentation to show the problems and possible solutions +in supporting immutable and read-only values in Go. + +## The main points of this proposal + +#### value roles + +This proposal introduces a **value role** concept. + +There are four kinds of value roles: +1. **reader** role. The values directly referenced by a reader value are all read-only values (from the view of the reader value). +1. **read-only** role. A read-only value may not be modified (through the read-only value itself). + If the read-only value can reference some values, then the read-only value is also a reader value, + so the values directly referenced by it are all read-only values too (from the view of the read-only value). +1. **writer** role. The values directly referenced by a writer value are all writable values (from the view of the writer value). +1. **writable** role. A writable value may be modified (through the writable value itself). + If the writable value can reference some values, then the writable value is also a writer value, + so the values directly referenced by it are all writable values too (from the view of the writeable value). + +Please note, +* A reader value, if it is not a read-only value, might be modifiable. +* A writer value, if it is not a writable value, might not be modifiable. + +Also note, values of some some types never reference any values, +or they are referencing some interval values which can't be modified in any ways in user code. +Such types are called **no-references types** below. +The **reader** and **writer** roles are non-sense for values of no-references types. +Some no-references type examples: +* basic types +* function types +* struct types with all field types are no-references types +* array types with no-references element types +* channel types with no-references element types + +#### assignment and conversion rules + +The following two rules stand if they stand without considering value roles. +* Reader or read-only values can be bound to read-only values. Read-only values can't be re-assigned to later. +* Reader or read-only values can be assigned to reader values. +* Writer or writable values can be **explicitly** converted to read-only values, not vice versa. (Here the **explicitly** means the `:role` suffix is required. See below for details.) + The conversion results can be assigned to reader values or bound to read-only values. + (About why writer or writable values can't be implicitly + converted to reader or read-only values, please read + [the problem mentioned in v9](README-v9.md#the-problem-when-reader-parameters-in-a-library-package-changed-to-writers).) +* Writer or writable values can be assigned to writer or writable values. + +Values of no-references types have adaptive roles when they are used as R-values (right-hand-side values). In other words, +* Values of no-references types are viewed as writer values when they are assigned to writer or writable values. +* Values of no-references types are viewed as reader values when they are assigned to reader or read-only values. + +#### new syntax set + +A notation form `:role` is introduced as value suffixes to indicate the roles of some values, where `role` in the form can only be `rr` (reader) or `ro` (read only). +* We can use `var:ro` to delcare read-only variables. + A declared read-only variable can be bound to an initial value. +* We can use `var:rr` to declare reader variables. +* We can use the form `v:ro` to explicitly convert writer or writable value `v` to a read-only value, and use `v:rr` to explicitly convert writer or writable value `v` to a reader value. + +For example, +``` +// x is a read-only (and reader) value. +// The elements of x are practical immutable values. +var:ro x = []int{1, 2, 3} +x = nil // error + +// y is reader value. +// The elements of y are practical immutable values, +// but y itself is modifiable. +var:rr y = []int{1, 2, 3} +y = nil // ok + +// w is a writable (and writer) value. +var w = []int{1, 2, 3} + +// z is a reader value. +// It and w share elements. +// The elements are read-only from the view of z, +// but they are writable from the view of w. +// So the elements are not immutable values. +var:rr z = w:ro +z[0] = 9 // error +w[0] = 9 // ok +``` + +Please note, in the above example, the `:rr` and `:ro` suffixes in the `var:rr z = w:ro` line are both required. +The reason is `w` is a roled value. Literal and constant values are all unroled values. + +Short declarations: +``` +var w = []int{1, 2, 3} +{ + // u is a read-only value, + // v is a reader value. + u, v := w:ro, w:rr + + ... // use u and v + + v = nil // ok +} +``` + +Although roles are properties of values, to make code (function prototype literals specificly) look compact, we can think of they are also properties of types. +The notation `T:role` is introduced to represent a type `T` with a specified role. +And to ease the syntax design, only `:rr` is allowed used as type suffixes. +However, please note, +* The notation `T:rr` is not allowed to appear in type declarations to declare reader types. +* The notation `T:rr` can only be used to specify function parameter and result types. + It may not be used to declare package-level variables and struct fields. +* In the `T:rr` notation, the `:rr` portion must be the last protion. + For example, the notation `[]*chan T:rr` can only mean `([]*chan T):rr`, + whereas `[]*chan (T:rr)`, `[]*((chan T):rr)` + and `[]((*chan T):rr)` are all invalid notations. + For more examples, it is not allowed to be used to + * specify the roles of struct field types. + * specify the roles of array/slice/map/channel element types. + * specify the roles of map key types. + * specify the roles of pointer base types. + +Although it is a non-sense, to avoid const-poisoning alike problems, +the `:rr` sufix is allowed to follow no-references types. + +A detail related to the `T:rr` notation need to be noted: +as long as one result of a function is specified with a read-only role, +then the result list part of the function prototype literal +must be enclosed in a pair of `()`. +For example, the notations `func() (T:rr)` and `func() T:rr` are different. +The former denotes a result type with specified role, +but the latter denotes a function type with specified role +(a non-sense role, for function types are no-references types). + +To avoid function declaration splitting problem, a **parameterized role** concept is introduced. +The notation `::r` (two colons) produces a parameterized role `r`. +For example, we can declare the `Split` function in the `bytes` standard package as +``` +// Here, the two "q" must be consistent. +func Split(v []byte::q, sep []byte:rr) ([][]byte::q) {...} +``` + +to avoid declaring it as two functions +``` +func SplitReader(v []byte:rr, sep []byte:rr) ([][]byte:rr) {...} +func SplitWriter(v []byte, sep []byte:rr) [][]byte {...} +``` + +## Detailed rules for values of all kinds of types + +#### safe pointers + +* Dereference of a reader pointer results a read-only value. +* Dereference of a writer pointer results a writable value. +* Taking address of an addressable a reader or read-only value results a reader pointer. +* Taking address of an addressable writer value results a writer pointer. + +Example: +``` +var:ro x = []int{1, 2, 3} + +func foo() { + y := &x // y is reader pointer variable + z := *y // z is deduced as a reader variable + w := x // w is deduced as a reader variable + z[0] = 9 // error: z[0] is read-only. + u := &z // u is like y, a reader pointer + + p1 := &x[1] // p1 is a reader pointer variable. + p2 := &z[1] // p2 is a reader pointer variable. + ... +} +``` + +Note: taking address of a string results a read-only pointer values. Example: +``` +var s = "hello word" +var:rr p1 = &bs[6] // ok +var p2 = &bs[6] // error +``` + +#### unsafe pointers + +* Reader pointers and writer pointers can be both converted to unsafe pointers. + This means the read-only rules built by this proposal can be broken by the unsafe mechanism. + (This is important for reflection implementation.) + +Example: +``` +func mut(x []int:rr) []int { + return *((*[]int)(unsafe.Pointer(&x))) +} +``` +#### structs + +* Fields of reader struct values are also reader values. +* Fields of read-only struct values are also read-only values. +* Fields of writer struct values are also writer values. +* Fields of writable struct values are also writable values. + +#### arrays + +* Elements of reader array values are also reader values. +* Elements of read-only array values are also read-only values. +* Elements of writer array values are also writer values. +* Elements of writable array values are also writable values. + +#### slices + +* Elements of reader or read-only slice values are read-only values. +* Elements of writer or writable slice values are writer values. +* We can't append elements to writer or writable slice values, + but can't append elements to reader or read-only slice values. +* Subslice: + * The subslice result of a reader or read-only slice is a read-only slice. + * The subslice result of a writer or writable slice is still a writer slice. + * The subslice result of a reader of read-only array is a read-only slice. + +Note: converting a string to a byte slice results a read-only byte slice value. Example: +``` +var s = "hello word" + +// "bs" is a reader byte slice. +// A clever compiler will not allocate a +// duplicate underlying byte sequence here. +var:rr bs = []byte(s) +``` + +Note, internally, the `cap` field of a reader byte slice is set to `-1` +if the byte slice is converted from a string, so that Go runtime knows +its elements are immutable. Converting such a reader byte slice to +a string doesn't need to duplicate the underlying bytes. + +#### maps + +* Elements of reader or read-only map values are read-only values. +* Elements of writer or writable map values are writer values. + (Each writable map element must be modified as a whole.) +* Keys (exposed in for-range) of reader or read-only map values are read-only values. +* Keys (exposed in for-range) of writer or writable map values are writer values. +* We can't append new entries to (or replace entries of, + or delete old entries from) reader or read-only map values. + +#### channels + +* Send + * We can only send reader or read-only values to a reader or read-only channel. + * We can only send writer or writable values to a writer channel. +* Receive + * Receiving from a reader channel results a read-only value. + * Receiving from a writer or writable channel results a writer value. + +#### functions + +Function parameters and results can be declared as reader variables. + +In the following function proptotype, parameter `x` and result `w` are declared as reader variables. +``` +func fa(x Tx:rr, y Ty) (z Tz, w Tw:rr) {...} +``` + +A `func()(T)` value is assignable to a `func()(T:rr)` value, not vice versa. + +A `func(T:rr)` value is assignable to a `func(T)` value, not vice versa. + +A declared `func(Tx::q) (Ty::q)` function can be used as values +and be assigned to `func(Tx:rr) (Ty:rr)` or `func(Tx) Ty` values. + +#### method sets + +We can delcare methods with recievers of reader types. + +When a method `M` is explicitly declared for reader type `T:rr`, then a corresponding method with the same name must be declared for writer type `T` by compilers **if no explicit method with the same name declared for writer type `T`**. The rule ensures that the method set of reader type `T:rr` is always a subset of writer type `T`. + +``` +func (T:rr) Mx() {} // explicitly declared. (A read-only method) +func(T) My() {} +/* +func (t T) Mx() {t:rr.Mx()} // implicitly declared. (A writer method) +*/ + +var t T +t.Mx() // <=> t:rr.Mx() +``` + +In the above code snippet, the method set of reader type `T:rr` contains one method: `rr.Mx`, however the method set of type `T` contains three method: `rr.Mx`, `Mx` and `My`. + +For type `T` and `*T`, if methods can be declared for them (either explicitly or implicitly), the method set of type `T:rr` is a subset of type `*T:rr`. + +When the receiver type is specified with a parameterized role in a method declaration, then two methods are declared actually. One is for writer receiver type, the other is for reader receiver type. +For example, +``` +func (T::q) M{Tx} Ty::q {...} +``` +is equivalent to +``` +func (T) M(Tx) Ty {...} +func (T:rr) M(Tx) Ty:rr {...} +``` + +#### interfaces + +An interface type can specify some read-only methods. For example: +``` +type I interface { + M0(Ta) Tb // a writer method + rr.M2(Tx) Ty // a reader method. + // NOTE: this is an exported method. +} +``` + +Similar to non-interface type, there is an implicit method `M2` specified for the writer version of the above shown interface type. +The method set specified by type `I` contains three methods actually, `M0`, `M2` and `rr.M2`. +The method set specified by type `I:rr` only contains one method, `rr.M2`. + +In the following code snippet, the type `T1` implements the interface `I` shown in the above code snippet, but the type `T2` doesn't. The reason is type `T2` has not a `rr.M2` method. +``` +type T1 struct{} +func (T1) M0(Ta) Tb {var b Tb; return b} +func (T1:rr) M2(Tx) Ty {var y Ty; return y} // the receiver type is a reader type. + +type T2 struct{} +func (T2) M0(Ta) Tb {var b Tb; return b} +func (T2) M2(Tx) Ty {var y Ty; return y} // the receiver type is a writer type. +``` + +Please note, the type `T3` in the following code snippet also implements `I`. +Please read the above function section for reasons. +``` +type T3 struct{} +func (T3) M0(Ta:rr) Tb {var b Tb; return b} +func (T3:rr) M2(Tx:rr) Ty {var y Ty; return y} +``` + +If a writer type `T` implements a writer interface type `I`, then the reader type `T:rr` also implements the reader interface type `I:rr` for sure. + +Boxing and assertion rules: +* The value boxing rules are like the assignment and conversion rules mentioned above. +* Assertion rules: + * A type assertion on a reader or read-only interface value results a read-only value. + * A type assertion on a writer or writable interface value results a writer value. + +## reflection + +Many function and method implementations in the `refect` package should be modified accordingly. +The `refect.Value` type shoud have a **_reader_** property, +and the result of an `Elem` method call should inherit the **reader** property +from the receiver argument. More about reflection. +For all details on reflection, please read the following reflection section. + +The current `reflect.Value.CanSet` method will report whether or not a value can be modified. + +A `reflect.ReaderValueOf` function is needed to create +`reflect.Value` values representing reader Go values. +Its prototype is +``` +func ReaderValueOf(i interface{}:rr) Value +``` +For the standard Go compiler, in implementaion, +one bit should be borrowed from the 23+ bits method number +to represent the `reader` proeprty. + +All parameters of type `reflect.Value` of the functions and methods +in the`reflect` package, including receiver parameters, +should be declared as reader variables. +However, the `reflect.Value` return results should be declared as writers. + +A `reflect.Value.ToReader` method is needed to +make a `reflect.Value` value represent a reader Go value. + +A `reflect.Value.ReaderInterface` method is needed, +it returns a reader interface value. +The old `Interface` method panics on reader values. + +A method `reflect.Type.Reader` is needed to get the reader version of a writer type. +A method `reflect.Type.Writer` is needed to get the writer version of a reader type. +The method sets of reader type `T:rr` is the subset of the writer type `T`. +Their respective other properties should be identical. + +A method `reflect.Type.Role` is needed, +it may return `Reader` or `Writer` (two constants). + + +## Compiler implementation + +I'm not familiar with the compiler development things. +It is just my feeling, by my experience, +that most of the rules mentioned in this proposal +can be enforced by compiler without big technology obstacles. + +At compile phase, compiler should maintain two bits to represent a value role, so that to make decisions according to the rules mentioned above. + +## Runtime implementation + +Except the changes mentioned in the above reflection section, +the impact on runtime made by this proposal is not large. + +Each internal method value should maintain a `reader` property. +This information is useful when boxing a reader value into an interface. + +As above has mentioned, the cap field of a reader byte slice should be set to `-1` if its byte elements are immutable. + +## Rationales + +#### Rationales of Go needs read-only and immutable values + +In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), +Russ mentions some inefficiencies caused by lacking of read-only values. +1. Now, the type of the only parameter of `io.Writer.Write` method is `[]byte`. + In fact, it should be a read-only parameter, for two reasons: + 1. so that a `Write` call can take a string value argument without making + a duplicate underlying bytes of the string argument. (More efficient) + 1. the current method prototype doesn't prevent a custom `Writer` + from modifying the elements of the passed `[]byte` argument. (More secure) +1. By specifying a parameter of a function as read only, users of the function + will clearly know the corresponding passed arguments will not be modified + in this funciton. (Better code as document) + +Besides these improvements, immutable values (this proposal supports) +can raise the security of Go code. +For example, by changing many exported `error` values in standard packages +to immutable values, the securities of Go programs are improved. + +Immutable slice values can also let compilers to do more +BCE (bounds check elimination) optimizations for them. + +The "Strengths of This Proposal" section in @jba's [propsoal](https://github.com/golang/go/issues/22876) +also makes a good summary of the benefits of read-only values. + +#### Rationales for the `T:rr` notation + +It is more compact than `var:rr T`. I think `func (Ta:rr) (Tx:rr)` has a better readibility than `func (var:rr Ta)(var:rr Tx)`. + +#### About the problems of read-only values mentioned by Russ + +In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), +Russ mentions some problems of read-only values. +This proposal provides solutions to avoid each of these drawbacks. + +1. **function duplication** + +Solved by role parameter. + +Please see the end of [the function section](#functions). + +2. **immutability and memory allocation** + +Solved by setting the capacities of immutable byte slices as `-1` at run time. + +Please see the end of [the slice section](#slices). + +3. **loss of generality** + +Solved by letting `interface {M(T:rr)}` implement `interface {M(T)}` +and letting `interface {M() T}` implement `interface {M() T:rr}`. + +Please see [the interface section](#interfaces) for details. + +#### about partial read-only + +Sometimes, people may need partial read-only for struct values. + +``` +type Counter struct { + n uint64 + + // mu will be always writable, + // even if its containing struct + // value is a read-only value. + mu sync.Mutex:writable +} + +func (c *Counter:rr) Value() uint64 { + // ok. c.mu will be modified, + // which is allowed, even if + // *c is a read-only value. + c.mu.Lock() + defer c.mu.Unlock() // ok + + return c.n +} +``` + +Partial read-only will make this proposal much more complex, so I decided not to support it. + +#### no-referenes types or no-references values + +The current proposal determines whether or not a value is referencing other values by checking whether or not its type is a no-references type. The checking happen at compile time. However, in fact, many run-time values can reference other values but at a certain time they are referencing any values. Such values are called no-references values. In theory, it is not a problem to assign such no-references values with reader role to writer values. But this can't be determined at compile time, so an invalid such assignment must panic. For simplity, the current proposal adopts no-references types instead of no-reference values. diff --git a/README.md b/README.md index 7912f0e..76b7485 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# A proposal to support read-only and immutable values in Go +This project proposes to support read-only and practical immutable values in Go. -Old versions: +Versions: * [the proposal thread](https://github.com/golang/go/issues/31464)([the old one](https://github.com/golang/go/issues/29422)), and the [golang-dev thread](https://groups.google.com/forum/#!topic/golang-dev/5M9F09S_k0g) * [v0: the initial proposal](README-v0.md) * [v1: the `var:N` version](README-v1.md) @@ -11,816 +11,11 @@ Old versions: * [v6: final+fixed. Without partial read-only](README-v6.md) * [v7: final+fixed. With partial read-only](README-v7.md) * [v8: const+reader/writer roles. Partial read-only removed](README-v8.md) -* [v9: final+reader/writer roles](README-v9.md) -* v9.1: final+reader/writer roles. Disallow passing writer values as reader arguments. (the currrent revision) +* [v9.0: final+reader/writer roles](README-v9.0.md) +* [v9.1: final+reader/writer roles](README-v9.1.md). Disallow passing writer values as reader arguments. +(v9.a+ is forked from v9.1, by removing the **final value** concept. So now this project contains two proposals actually.) -_Comparing to the last revision v9, this version forbids passing writer values as reader arguments (including receiver arguments), -to avoid [the problem mentioned in v9](README-v9.md#the-problem-when-reader-parameters-in-a-library-package-changed-to-writers)._ +* [v9.a, reader + read-only values](README-v9.a.md) -This revision is a little Go 1 incompatible, for it needs a new keyword `final`. -Any criticisms and improvement ideas are welcome, for -* I have not much compiler-related knowledge, so the following designs may have flaws. -* I haven't found a perfect syntax notation set for this proposal yet. - -## The problems this proposal tries to solve - -The problems this proposal tries to solve: -1. no ways to declare package-level exported immutable non-basic values. -1. no ways to declare read-only function parameters and results. -1. many inefficiencies caused by lacking of immutable and read-only values. - -Detailed rationales are listed in [at the end of this proposal](#rationales). -Some solutions for the drawbacks mentioned by Russ are also listed there. - -Basically, this proposal can be viewed as a combination -of [issue#6386](https://github.com/golang/go/issues/6386) -and [issue#22876](https://github.com/golang/go/issues/22876). - -This proposal also has some similar ideas with -[evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A) -written by Russ. - -However, this proposal has involved so much that -it has become into a much more practical solution with -more ideas and details than the just mentioned ones. - -This propsoal is not intended to be accepted, at least soon. -It is more a documentation to show the problems and possible solutions -in supporting immutable and read-only values in Go. - -## The main points of this proposal - -We know each value has a property, `self_modifiable`, -which means whether or not that value itself is modifiable. - -This proposal will add a new value property `ref_modifiable` for each value. -This property means whether or not the values referenced -(either directly or indirectly) by a value are modifiable. -The `ref_modifiable` property will be called a value role later. - -The permutation of thw two properties results 4 genres of values: -1. `{self_modifiable: true, ref_modifiable: true}`. - Such as variables. -1. `{self_modifiable: true, ref_modifiable: false}`. - No such Go values currently. -1. `{self_modifiable: false, ref_modifiable: true}`. - Such as composite literals. - (In fact, all constants in JavaScript and - all final variables decalred in Java belong to this genre.) -1. `{self_modifiable: false, ref_modifiable: false}`. - No such Go values currently. - -(Note, in fact, we can catagory declared function values, method values -and constant basic values into either the 3rd or the 4th genre.) - -This proposal will let Go support values of the 2nd and 4th genres, -and extend the value range of the 3rd genre. - -#### variables and finals - -This proposal treats the `self_modifiable` as a value property. -* `{self_modifiable: true}` values (variables) are declared with `var`. -* `{self_modifiable: false}` values (finals) are declared with `final` - Like named constants, a named final must be bound a value in its declaration. - But unlike named constants, finals can be values of any type, not limited to values of basic types. - We can view finals as runtime constants. - Please note that, although a final itself can't be modified, - the values referenced by the final might be modifiable. - (Much like JavaScript `const` values and Java `final` values.) - -Most intermediate results in Go should be viewed as final values, -including function returns, operator operation evaluation results, -explicit value conversion results, etc. - -Finals (themselves) are immutable values. -Note, although a final itself is an immutable value, -whether or not the values referenced by the final are immutable -values depends on the specified role (see the next section) of the final. - -There is not a short final declartion form. -Shorted declared values are all variables. -Function parameters and results also can't be delcared as finals. - -#### value roles: reader and writer - -This proposal proposes to add a value role concept to -denote the `ref_modifiable` property. -Although roles are properties of values, -to ease the syntax designs, we can think they are also properties of types. - -The notation `T:reader` is introduced to represent a reader type. -Its values are called reader values. -The notation can be used to declare package-level variables/finals, -local variables/finals, and function parameter/result variables. -But it can't be used to specifiy struct field types. -Fields of a struct value will inherit the roles from the struct value. - -There is not the `T:writer` notation. -The *writer role* concept does exist. -The raw `T` notation means a writer type (in non-struct-field declarations). - -Thw word `reader` can be either a keyword or not. -If it is designed as not, then it can be viewed as a predeclared role. - -The meanings of **reader** and **writer** values: -* All values referecned by a reader value are read-only (and also reader) values - (from the view of the reader value). - In other words, a reader value represents a read-only value chain, - and the reader value is the head of the chain. - Note, the reader head itself might be a variable, which is not a read-only value. -* All values referecned by a writer value are writable (and also writer) values - (from the view of the writer value). - In other words, a writer value represents a writable value chain, - and the writer value is the head of the chain. - Note, the writer head itself might be a final, which is a read-only value. - -Some details about the `T:reader` notation need to be noted: -1. `:reader` is not allowed to appear in type declarations, - except it shows up as function parameter and result roles. - * For example, `type T []int:reader` is invalid, - but `type T func (T:reader)` is valid. - * And please note that, as long as one result of a function is a reader value, - then the result list part of the function proptotype literal - must be enclosed in a pair of `()`. For example, - `func() T:reader` is invalid, it must be `func() (T:reader)`. -1. The notation `[]*chan T:reader` can only mean `([]*chan T):reader`, - whereas `[]*chan (T:reader)`, `[]*((chan T):reader)` - and `[]((*chan T):reader)` are all invalid notations. -1. Some kinds of types are always non-reader types, including basic types and function types. - So, `:reader` is not allowed to follow such type names and literal. - For example (again), `func() T:reader` is invalid. - The saying of "the reader version of a non-reader type" does exist. - The reader version of a non-reader type is the non-reader type itself. -1. Struct types which all field types are non-reader types - and array/channel types with non-reader element types - are also non-reader types. But, to avoid const-poisoning alike problems, - such type notations can be followed with `:reader`. - But the `:reader` suffix is a non-sense for such types. - For example, `[5]struct{a int}` and `[5]struct{a int}:reader` - have no differences. - -A notation `v:reader` is introduced to convert a writer value `v` to a reader value, -The `:reader` suffix can only follow r-values (right-hand-side values). - -You may have got it, a value hosted at a specified memory address may -represent as a read-only value or a writable value, depending on context. -So a non-final read-only values might be not an immutable value. -(But there are really some non-final read-only values which are immutable values. -Please read the following for such examples.) - -#### assignment and conversion rules - -Above has mentioned: -* a named final must be bound to a value in its declaration. - It can't be assigned to again later. -* a writer value is assignable to a reader variable, but - a writer value can't be passed as a reader argument, it must be - explicitly converted to a reader value to be used as a reader argument. -* a reader value is not assignable to a writer variable. -* a writer value can be converted to a reader value, but not vice versa. - -#### some examples - -An example: -``` -{ - var x = []*int{new(int)} // x is a writer variable - final y = x // y is a writer final - var z []*int:reader = x // z is a reader variable - final w = y:reader // w is a reader final - - // x, y, z and w share elements. - - x[0] = new(int); *x[0] = 123 // ok - x = nil // ok - println(*z[0]) // 123 - - y[0] = new(int); *y[0] = 789 // ok - y = nil // error: y is a final - println(*w[0]) // 789 - - *z[0] = 555; z[0] = new(int) // error: z[0] and *z[0] are read-only - z = nil // ok - - *w[0] = 555; w[0] = new(int) // error: w[0] and *w[0] are read-only - w = nil // error: w is a final - - x = z // error: reader value z can't be assigned to writer value x -} -``` - -In the above, the elements of the slices are not immutable values. -However, in the following example, the slice elements are immutable. -``` -{ - var s = []int{1, 2, 3}:reader - s[0] = 9 // error: s[0] is read-only - - // S and its elements are both immutable. - final S = []int{1, 2, 3}:reader -} -``` - -More examples: -``` -// An immutable error value. -final FileNotExist = errors.New("file not exist"):reader - -var n int:reader // error: int is a non-reader type - -// Two functions with read-only parameters and results. -// All parameters are varibles. -// Note that "chan int" is a non-reader type. -func Foo(m http.Request:reader, n map[string]int:reader) (o []int:reader, p chan int) {...} -func Print(values ...interface{}:reader) {...} - -// Some short declartions. The items on the left sides -// are all variables. No ways to short delcare finals. -{ - oldA, newB := va, vb:reader // newB is a reader variable - - // Explicit conversions. - // The four lines are equivalent to each other. - newX, oldY := (Tx:reader)(va), vy - newX, oldY := (Tx(va)):reader, vy - newX, oldY := Tx(va:reader), vy - newX, oldY := Tx(va):reader, vy -} -``` - -#### about final, reader, read-only, and immutable values - -From the above descriptions and explanations, we know: -* a final itself is not only a read-only value, it is also an immutable value. -* a reader value may be a variable or a final, - so it may be read-only or writable. -* a writer value may be a variable or a final, - so it may be read-only or writable. -* some read-only values are immutable values, but most are not. - -No data synchronizations are needed in concurrently reading immutable values, -but data synchronizations are still needed when concurrently reading -a read-only value which is not an immutable value. - -NOTE: the above mentioned "immutable values" all means "practically immutable values". -Such values may be modified through unsafe ways. - -## Detailed rules for values of all kinds of types - -#### safe pointers - -* Dereference of a reader pointer results a read-only value. -* Dereference of a writer pointer results a writable value. -* Taking address of an addressable final or a reader value results a reader pointer. -* Taking address of an addressable writer value results a writer pointer. - -Example: -``` -final x = []int{1, 2, 3} - -func foo() { - y := &x // y is reader pointer variable of type *[]int:reader. - z := *y // z is deduced as a reader variable of type []int:reader. - w := x // w is deduced as a writer variable of type []int. - z[0] = 9 // error: z[0] is read-only. - u := &z // u is like y. - - p1 := &x[1] // p1 is a writer pointer variable. - p2 := &z[1] // p2 is a reader pointer variable. - ... -} -``` - -#### unsafe pointers - -* Reader pointers and writer pointers can be both converted to unsafe pointers. - This means the read-only rules built by this proposal can be broken by the unsafe mechanism. - (This is important for reflection implementation.) - -Example: -``` -func mut(x []int:reader) []int { - return *((*[]int)(unsafe.Pointer(&x))) -} -``` -#### structs - -* Elements of reader struct values are also reader values. -* Elements of writer struct values are also writer values. -* Elements of read-only struct values are also read-only values. -* Elements of writable struct values are also writable values. - -#### arrays - -* Elements of reader array values are also reader values. -* Elements of writer array values are also writer values. -* Elements of read-only array values are also read-only values. -* Elements of writable array values are also writable values. - -#### slices - -* Elements of reader slice values are read-only and reader values. -* Elements of writer slice values are writable and writer values. -* We can't append elements to reader slice values. -* Subslice: - * The subslice result of a reader slice is still a reader slice. - * The subslice result of a writer slice is still a writer slice. - * The subslice result of a final or reader array is a reader slice. - -Example 1: -``` -type T struct { - a int - b *int -} - -// The type of x is []T:reader. -var x = []T{{123, nil}, {789, new(int)}}:reader - -func foo() { - x[0] = nil // error: x[0] is read-only. - x[0].a = 567 // error: x[0] is read-only. - y := x[0] // y is a reader value of type T:reader. - y.a = 567 // ok - *y.b = 567 // error: y.b is read-only - y.b = nil // ok - z := x[:1] // liek x, z is reader slice. - x = nil // ok - y = T{} // ok - - final w = x // w is a reader final. - u := w[:] // u is a reader slice variable. - - // v is a writer slice final. - final v = []T{{123, nil}, {789, new(int)}} - v = nil // error: v is a final - v[1] = T{} // ok - - _ = append(u, T{}) // error: can't append to reader slices - _ = append(v, T{}) // ok - - ... -} -``` - -Example 2: -``` -var x = []int{1, 2, 3} - -// External packages have no ways to modify elements of x (through S). -final S = x:reader - -// The elements of R can't even be modified in current package! -// It and its elements are all immutable values. -final R = []int{7, 8, 9}:reader - -// Q itself can't be modified, but its elements can. -final Q = []int{7, 8, 9} -``` - -Example 3: -``` -var s = "hello word" - -// "bs" is a reader byte slice. -// A clever compiler will not allocate a -// duplicate underlying byte sequence here. -var bs = []byte:reader(s) // <=> []byte(s):reader -{ - pw := &s[6] // pw is reader poiner whose base type is "byte". - pw = &bs[6] // ok -} -``` - -Note, internally, the `cap` field of a reader byte slice is set to `-1` -if the byte slice is converted from a string, so that Go runtime knows -its elements are immutable. Converting such a reader byte slice to -a string doesn't need to duplicate the underlying bytes. - -#### maps - -* Elements of reader map values are read-only and reader values. -* Elements of writer map values are writable and writer values. - (Each writable map element must be modified as a whole.) -* Keys (exposed in for-range) of reader map values are reader values. -* Keys (exposed in for-range) of writer map values are writer values. -* We can't append new entries to (or replace entries of, - or delete old entries from) reader map values. - -Example: -``` -type T struct { - a int - b *int -} - -// x and its entries are all immutable values. -final x = map[string]T{"foo": T{a: 123, b: new(int)}}:reader - -bar(x) // ok - -func bar(v map[string]T:reader) { // v is a reader variable - // Both v["foo"] and v["foo"].b are both reader values. - *v["foo"].b = 789 // error: v["foo"].b is read-only - v["foo"] = T{} // error: v is a reader map - v["baz"] = T{} // error: v is a reader map - - // m will be deduced as a reader map variable. - // That means as long as one element or one key is a reader - // value in a map literal, then the map is also a reader value. - m := map[*int]*int { - new(int): new(int):reader, - new(int): new(int), - new(int): new(int), - } - for a, b := range m { - // a and b are both reader values of type *int:reader. - - *a = 123 // error: *a is read-only - *b = 789 // error: *b is read-only - } -} -``` - -#### channels - -* Send - * We can't send values to final channels. - * We can send values of any genres to a reader channel. - * We can only send writer values to a writer channel. -* Receive - * We can't receive values from final channels. - * Receiving from a reader channel results a reader value. - * Receiving from a writer channel results a writer value. - -Example: -``` -final ch = make(chain *int, 1) - -func foo(c chan *int:reader) { - x := <-c // ok. x is a reader variable of type *int:reader. - y := new(int) - c <- y // ok - - ch <- x // error: ch is a final channel - <-ch // error: ch is a final channel - ... -} -``` - -#### functions - -Function parameters and results can be declared as reader variables. - -In the following function proptotype, parameter `x` and result `w` are declared as reader variables. -``` -func fa(x Tx:reader, y Ty) (z Tz, w Tw:reader) {...} -``` - -A `func()(T)` value is assignable to a `func()(T:reader)` value, not vice versa. - -A `func(T:reader)` value is assignable to a `func(T)` value, not vice versa. - -To avoid function duplications like the following code shows: -``` -// split writer byte slices -func Split_1(v []byte, sep []byte:reader) [][]byte {...} - -// split reader byte slices -func Split_2(v []byte:reader, sep []byte:reader) ([][]byte:reader) {...} -``` - -A role parameter concept is introduced, -so that the above two function can be declared as one: -``` -func Split(v []byte::q, sep []byte:reader) ([][]byte::q) {...} -``` - -Here, `:q` is called a role parameter. -Its name can be arbitrary non-blank identifier, -but the two occurrences must be consistent. -Short role parameter names are recommended, such as `p` and `q`. - -Use the `Split` function. -``` -{ - var x = []byte{"aaa/bbb/ccc/ddd"} - var y = Split(x, []byte("/")) // call the writer version - // y is a writer value. - var z = Split(x:reader, []byte("/")) // call the reader version - // z is a reader value. - - // Use Split function as values. - var fw = Split{q: writer} // I haven't got better syntax idea yet. - var fr = Split{q: reader} - _ = fr(x:reader, []byte("/")) -} -``` - -#### method sets - -We can delcare methods with recievers of reader types. - -The method set of reader type `T:reader` is always a subset of writer type `T`. -This means when a method `M` is explicitly declared for reader type `T:reader`, -then a corresponding implicit method with the same name -will be declared for writer type `T` by compilers. -``` -func (T:reader) M() {} // explicitly declared. (A reader method) -/* -func (t T) reader.M() {t:reader.M()} // implicitly declared. (A writer method) -*/ -/* -func T:reader.M(t T:reader) {t.M()} // an implicitly declared function. -*/ - -var t T -t:reader.M() -// <=> -T:reader.M(t:reader) -``` - -Note, to avoid [the problem mentioned in v9](README-v9.md#the-problem-when-reader-parameters-in-a-library-package-changed-to-writers), the following method/function calls are invalid: -``` -var t T -t.M() -// <=> -T:reader.M(t) -// <=> -T.M(t) -``` - -In the above code snippet, the method set of reader type `T:reader` contains one method: `reader.M`, -however the method set of type `T` contains two method: `reader.M` and `M`. - -For type `T` and `*T`, if methods can be declared for them (either explicitly or implicitly), -the method set of type `T:reader` is a subset of type `*T:reader`. -(Or in other words, the method set of type `T` is a subset of type `*T` -if type `T` is not an interface type.) - -#### interfaces - -An interface type can specify some read-only methods. For example: -``` -type I interface { - M0(Ta) Tb // a writer method - - reader.M2(Tx) Ty // a reader method. - // NOTE: this is an exported method. -} -``` - -Similar to non-interface type, if a reader interface type -explicitly specified a reader method `reader.M`, -it also implicitly specifies a writer method with the same name `M`. - -The method set specified by type `I` contains three methods, `M0`, `reader.M2` and `M2`. -The method set specified by type `I:reader` only contains one method, `reader.M2`. - -When a method is declared for a concrete type to implement a reader method, -the type of the receiver of the declared method must be a reader type. -For example, in the following code snippet, -the type `T1` implements the interface `I` shown in the above code snippet, -but the type `T2` doesn't. -``` -type T1 struct{} -func (T1) M0(Ta) Tb {var b Tb; return b} -func (T1:reader) M2(Tx) Ty {var y Ty; return y} // the receiver type is a reader type. - -type T2 struct{} -func (T2) M0(Ta) Tb {var b Tb; return b} -func (T2) M2(Tx) Ty {var y Ty; return y} // the receiver type is a writer type. -``` - -Please note, the type `T3` in the following code snippet also implements `I`. -Please read the above function section for reasons. -``` -type T3 struct{} -func (T3) M0(Ta:reader) Tb {var b Tb; return b} -func (T3:reader) M2(Tx:reader) Ty {var y Ty; return y} -``` - -If a writer type `T` implements a writer interface type `I`, -then the reader type `T:reader` also implements the reader interface type `I:reader` for sure. - -* Dynamic type - * The dynamic type of a writer interface value is a writer type. - * The dynamic type of a reader interface value is a reader type. -* Box - * No values can be boxed into final interface values (except the initial bound values). - * reader values can't be boxed into writer interface values. - * Values of any genres can be boxed into a reader interface value. -* Assert - * A type assertion on a reader interface value results a reader value. - For such an assertion, its syntax form `x.(T:reader)` can be simplified as `x.(T)`. - * A type assertion on a writer interface value results a writer value. - -For this reason, the `xyz ...interface{}` parameter declarations of all the print functions -in the `fmt` standard package should be changed to `xyz ...interface{}:reader` instead. - -Role parameters don't work for receiver parameters. - -Example: -``` -var x = []int{1, 2, 3} -var y = [][]int{x, x}:reader -var u interface{} = x // ok -u = y // error: can't assign a reader value to a writer value. -var v interface{}:reader = y // ok. v is deduced as a reader interface value. -var w = v.([][]int) // ok. Like y, w is a reader value of type [][]int:reader. -v = x // ok -var s = v.([]int) // ok, u is a reader value of type []int:reader. -var t = v.([]int:reader) // ok, equivalent to the above one. -var q = u.([]int:reader) // ok, Assert + convert. -var r = u.([]int):reader // ok, Assert then convert. Equivalent to the above one. -``` - -Another eample: -``` -type T0 []int -func (T0) M([]int) []int - -type T1 []int -func (T1) M([]int:reader) []int - -type T2 []int -func (T2) M([]int) ([]int:reader) - -type T3 []int -func (T3) M([]int:reader) ([]int:reader) - -type I interface { - M([]int) []int:reader -} - -// T0, T1, T2, and T3 all implement I. -var _ I = T0{} -var _ I = T1{} -var _ I = T2{} -var _ I = T3{} -``` - -#### reflection - -Many function and method implementations in the `refect` package should be modified accordingly. -The `refect.Value` type shoud have a **_reader_** property, -and the result of an `Elem` method call should inherit the **reader** property -from the receiver argument. More about reflection. -For all details on reflection, please read the following reflection section. - -The current `reflect.Value.CanSet` method will report whether or not a value can be modified. - -A `reflect.ReaderValueOf` function is needed to create -`reflect.Value` values representing reader Go values. -Its prototype is -``` -func ReaderValueOf(i interface{}:reader) Value -``` -For the standard Go compiler, in implementaion, -one bit should be borrowed from the 23+ bits method number -to represent the `reader` proeprty. - -All parameters of type `reflect.Value` of the functions and methods -in the`reflect` package, including receiver parameters, -should be declared as reader variables. -However, the `reflect.Value` return results should be declared as writers. - -A `reflect.Value.ToReader` method is needed to -make a `reflect.Value` value represent a reader Go value. - -A `reflect.Value.ReaderInterface` method is needed, -it returns a reader interface value. -The old `Interface` method panics on reader values. - -A method `reflect.Type.Reader` is needed to get the reader version of a writer type. -A method `reflect.Type.Writer` is needed to get the writer version of a reader type. -The method sets of reader type `T:reader` is the subset of the writer type `T`. -Their respective other properties should be identical. - -A method `reflect.Type.Genre` is needed, -it may return `Reader` or `Writer` (two constants). - - -## Compiler implementation - -I'm not familiar with the compiler development things. -It is just my feeling, by my experience, -that most of the rules mentioned in this proposal -can be enforced by compiler without big technology obstacles. - -At compile phase, compiler should maintain two bits for each value. -One bit means whether or not the value itself can be modified. -The other bit means whether or not the values referenced by the value can be modified. - -## Runtime implementation - -Except the next to be explained reflection section, the impact on runtime -made by this proposal is not large. - -Each internal method value should maintain a `reader` property. -This information is useful when boxing a reader value into an interface. - -As above has mentioned, the cap field of a reader byte slice should -be set to `-1` if its byte elements are immutable. - -## Rationales - -#### Rationales of Go needs read-only and immutable values - -In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), -Russ mentions some inefficiencies caused by lacking of read-only values. -1. Now, the type of the only parameter of `io.Writer.Write` method is `[]byte`. - In fact, it should be a read-only parameter, for two reasons: - 1. so that a `Write` call can take a string value argument without making - a duplicate underlying bytes of the string argument. (More efficient) - 1. the current method prototype doesn't prevent a custom `Writer` - from modifying the elements of the passed `[]byte` argument. (More secure) -1. By specifying a parameter of a function as read only, users of the function - will clearly know the corresponding passed arguments will not be modified - in this funciton. (Better code as document) - -Besides these improvements, immutable values (this proposal supports) -can raise the security of Go code. -For example, by changing many exported `error` values in standard packages -to immutable values, the securities of Go programs are improved. - -Immutable slice values can also let compilers to do more -BCE (bounds check elimination) optimizations for them. - -The "Strengths of This Proposal" section in @jba's [propsoal](https://github.com/golang/go/issues/22876) -also makes a good summary of the benefits of read-only values. - -#### Rationales for the `T:reader` notation - -1. It is less discrete than `reader T`. I think `func (Ta:reader) (Tx:reader)` has a better readibility than `func (reader Ta)(reader Tx)`. -1. It conforms to Go type literal design philosophy: more importants shown firstly. -1. It saves one keyword. - -#### About the problems of read-only values mentioned by Russ - -In [evaluation of read-only slices](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A), -Russ mentions some problems of read-only values. -This proposal provides solutions to avoid each of these drawbacks. - -1. **function duplication** - -Solved by role parameter. - -Please see the end of [the function section](#functions). - -2. **immutability and memory allocation** - -Solved by setting the capacities of immutable byte slices as `-1` at run time. - -Please see the end of [the slice section](#slices). - -3. **loss of generality** - -Solved by letting `interface {M(T:reader)}` implement `interface {M(T)}` -and letting `interface {M() T}` implement `interface {M() T:reader}`. - -Please see [the interface section](#interfaces) for details. - -#### About partial read-only - -Sometimes, people may need partial read-only for struct values. -[An older revision](README-v7.md#structs) of this proposal supports this feature, -but it is removed from the currrent revision for it brings many complexisites -and may cause some design flaws. - -``` -type Counter struct { - mu sync.Mutex:writable // will be always writable (and also a writer), - // even if its containing struct value is a read-only. - n uint64 -} - -{ - final c Counter - c.mu.Lock() // error: c.mu is read-only - - var p = &c // p is a reader pointer of type *Counter:reader - p.mu.Lock() // ok by the rule, but it makes c become a non-immutable value, - // which may cause some confusions. - // To avoid such cases happening, we can forbid taking addresses - // of finals, but I feel this trade-off isn't worth it. -} -``` - -To support partial read-only, the following rules need to be added: -1. finals are always unaddressable. -1. values of `struct{t T:writable}` can be converted/assignable to `struct{t T}`. -1. function values - 1. values of `func (struct{t T})` can be converted/assignable to `func (struct{t T:writable})`. - 1. values of `func () struct{t T:writable}` can be converted/assignable to `func () struct{t T}`. - 1. values of `func (struct{t T})` and `func (struct{t T:writable}):reader` can't be converted to each other. - -Another simpler rule design is to forbid the conversions mentioned in the 2nd and 3rd rules. - -This means, the addressable final feature and the partial read-only feature are mutually exclusive. -I prefer keeping the addressable final feature. -This feature will break less user code, -for some user code may take the addresses of many error values declared in std packages. -This feature will continue to make such code valid.