-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: a proposal to support final values and read-only parameters/results in Go #29422
Comments
I forget an important thing. The rules in the proposal don't apply to basic values. Or we need to introduce a new keyword |
OK, it looks the |
Updated the "assign immutable values to mutable values" part. |
Two use cases which become possible with this proposal: fixed bs = []byte(aString) // no underlying bytes are copied.
fixed pb = &aString[0] // string bytes are addressable. |
I have an interesting idea to extend this proposal but will make this proposal more complex. The An example: fixed:0 p = new(int)
pp := &p // <=> fixed:1 pp = &p <=> fixed pp = &p
ppp := &pp // <=> fixed:2 ppp = &pp
pppp := &ppp // <=> fixed:3 pppp = &ppp
// A value declared with fixed:N can only be dereferenced N times
// when its dereference is used as a destination value.
pp = new(**int) // ok
*pp = new(*int) // ok
**pp = new(int) // error: immutable
ppp = new(***int) // ok
*ppp = new(**int) // ok
**ppp = new(*int) // ok
***ppp = new(int) // error: immutable
pppp = new(****int) // ok
*pppp = new(***int) // ok
**pppp = new(**int) // ok
***pppp = new(*int) // ok
****pppp = new(int) // error: immutable
// a fixed:N value can be assigned to a fixed:M value if M <= N + 1.
// [update]: SORRY, the above old line is wrong. The following line is right.
// a fixed:N value can be assigned to a fixed:M value if M <= N.
fixed:2 pppp2 = pppp // ok
fixed:3 pppp3 = pppp // ok
fixed:4 pppp4 = pppp // error
fixed:5 pppp5 = pppp // error
|
Ah! If we replace all the |
the comment previous to last is updated and corrected. |
The The following example shows some type T *T
var t T
t = &t
var:0 x0 = t
x0 = t // error: "x0" is immutable.
var:1 x1 = t
x1 = t // ok. "x1" is mutable.
*x1 = t // error: "*x1" is immutable.
var:2 x2 = t
x2 = t // ok. "x2" is mutable
*x2 = t // ok. "*x2" is mutable
**x2 = t // error: "**x2" is immutable.
var:3 x3 = t
x3 = t // ok. "x3" is mutable
*x3 = t // ok. "*x3" is mutable
**x3 = t // ok. "**x3" is mutable
***x3 = t // error: "***x3" is immutable.
// var <=> var:∞
var x = t
x = t // ok
*x = t // ok
**x = t // ok
***x = t // ok
****x = t // ok
*****x = t // ok
*******************************x = t |
How would this interact with things already in Go currently such as |
It has not any relations to (and any impacts on) the current constant things. It is a competitor proposal to many other immutable type proposals, for this proposal and those proposals all try to solve the same problems. |
I understand a bit more about how this works. It is not illustrated anywhere how this interacts with function parameters, which is the main use-case for read-only variables. It looks like |
We can think that, currently, I have mentioned immutable function parameters and results in the starting comment. I re-post it here (by adding two
Or the revised version:
In fact, argument passing and result returning are just general value assignments. |
adjusted my last reply comment. |
The general value assignment rule is: a |
A BTW idea: maybe we can merge the proposal described by @niemeyer in this comment into this read-only value proposal. In short, we can specify the mutable depth for new declared variables in short variable declarations as: N:newV0, M:newV1, oldV = x, y, z The mutable depth prefixes of a new declared variable can be omitted, which means its mutable depth is infinite. (But the Similarly, we can use the mutable depth prefixes in standard variable declarations. One difference to short variable declaration is, the [update]: mutable depth conversions: type T [][]int
var a = [][]int{{1, 2, 3}, {7, 8, 9}}
var:1 b = a // type of b is also [][]int
// <=> var 1:b = a
c := (T:1)(a) // type of c is T, it is a var:1 The following function prototypes are equivalent. func(1:a, 2:b T1, 1:c T2) (1:x, 1:y T3)
func(1:T1, 2:T1, 1:T2) (x, y 1:T3) |
Arbitrary mutable depth may be an interesting idea, but it brings some complexities to Go, and it might be not much useful in practice. Maybe only supporting mutable depth 1 and 0 is enough. I have another interesting (but some verbose) idea to extend the genres of values: var.var // self is modifiable, references are also modifiable.
// It can be shortened as the current used "var".
var.fixed // self is modifiable, references are unmodifiable.
// It is equivalent to the "fixed" in the starting comment.
// It can be shortened as "fixed".
fixed.var // self is unmodifiable, references are modifiable.
// The usefulness of this value genre is limited.
fixed.fixed // self is unmodifiable, references are also unmodifiable.
// It is equivalent to the "fixed!" in the starting comment. [update]: one shortcoming of these denotations is they make the function prototype literals look verbose (but more readable) than the func(fixed a, b T1, fixed c T2) (fixed x, y T3)
func(fixed T1, fixed T1, fixed T2) (fixed T3, fixed T3) [update 2]: another shortcoming of these long denotations is they make short variable declarations look verbose (but might be also more readable) than the newV0, newV1, oldV := fixed(x), fixed(y), z |
Updated the first comment for the channel section. Updated several other comments. |
Updated the first comment again by adding rules for values of more kinds of types. |
I believe, it is better to go with types and type conversion. This proposal any way could not be implemented without types (because of Therefore, why don't go with types in first place? |
I made one: #29392
why? |
fixed:0 p = new(int)
pp := &p // <=> fixed:1 pp = &p <=> fixed pp = &p
ppp := &pp // <=> fixed:2 ppp = &pp
pppp := &ppp // <=> fixed:3 pppp = &ppp
var val interface{} = pppp // have to put type into interface{} |
The last line fails to compile. |
Could I ask you not to use the issue comments as a changelog? It obscures the discussion, and generates unnecessary notifications to anyone subscribed to the thread. You can keep a changelog at the end of the main text. Thanks! |
Sorry, here are so few discussions that I think not many people subscribed to this thread. :) |
TBH, I like all the comments go101 is posting. I am looking forward for good immutability support in Go and reading the progress he/she is doing on this matter excites me! |
Hello. I briefly looked at your proposal, not quite thoroughly, but may I ask some questions so I get a more complete idea of the proposal? |
I would say
More accurately speaking, all the values referenced by it are also
Mutable arguments can be passed to
The interface part is really the most complicated part of the proposal, though I still think the rules are natural. (BTW, I just modified the method set and interface sections of the proposal a little.) The key point of the interface rules design is to ensure type For the 4 types declared below,
The following implementation relations exist:
type X interface {
M0()
fixed.M1()
}
type Y interface {
M0()
M1()
}
type A struct{}
func (A) M0() {}
func (A.fixed) M1() {}
type B struct{}
func (B) M0() {}
func (B) M1() {} |
@go101 Thanks for the explanation. If I didn't get it wrong, every interface has a fixed counterpart and some of the methods has a I see two problems with the design:
It suggests that
|
The answer is surely NO. I think the rules are specified clearly in the proposal. Immutable values can only be boxed in immutable interfaces. Mutable values can also boxed into immutable interfaces, but we can think the mutable values are implicitly converted to fixed ones before boxing.
This is not true any longer by introducing the proposal. When you call a method on an immutable interface value, you DO expect the dynamic receiver value will not be modified. (Edit: expect the values referenced by the dynamic receiver value will not be modified.) |
I am not convinced that such property can be expected from an immutable value. Immutable values are not read-only, and vice versa, in the following ways:
type Object [16]byte
var database = OpenMyDatabase()
func (id Object.fixed) Get() string {
return database.Get(id)
}
func (id Object.fixed) Set(s string) {
database.Set(id, s)
}
|
You can't modify the values referenced by a So the immutability of another storage must be enforced through the immutable values. The other storage may be modified through other mutable values, but it can't through In your example, the types of the first arguments of the methods of
I'm confused by the your definition of |
The
The thing here is that Such data structures can't fit into an immutable value even if they are "read-only" as a programmer normally expects from an immutable value. |
It looks we are talking about two different concepts of "immutability". This proposal only makes some immutability guarantees for Go values in the language level, not application logic specified level.
I'm sorry, the "reference" concept used in this proposal is not the same as your mentioned "reference". In this proposal, a "reference` means a pointer pointing to another value. It is not the "reference" in logic.
This may be true. A |
One concept is the concept mentioned in the proposal, the other concept is what I believe programmers normally expect from "immutability" or the term "read-only". The examples illustrate the difference between the two concepts. And it is subtle.
Maybe I didn't talk about this point clearly. It means that the read-only operation itself writes to the struct as part of the operation. It cannot read without doing the write. The problem with such two different concepts is that it confuses programmers and add unnecessary complexity to the code, since most functions expect a "read-only" parameter but it differs from the "immutable" property in the proposal. Take |
What the "fixed" described in the proposal is just the "read-only" described in your last comment. Maybe it is a fault for the proposal to use the "immutable" word in descriptions. Please note, a The proposal does support another "immutable" concept. Values declared with |
I modified the proposal to try to avoid using the "immutable" word. |
@HEWENYANG about the This is really a problem. A simpler case: type CounterA struct {
mu sync.Mutex
n uint64
}
func (c *CounterA.fixed) Get() uint64 {
c.mu.Lock() // error: the receiver of Lock is not fixed.
v := c.n
c.mu.Unlock()
return v
} The current rules are over-strict here. Here, we expect the |
How about to add one more notation? type CounterA struct {
mu sync.Mutex.unfixable
n uint64
} The All unsafe pointer types are viewed as such
type CounterB struct {
mu sync.Mutex
n uint64
} Edit 2: |
As this proposal has mentioned, each value has two properties, To make it possible to fully control the properties of struct fields, the full literal form of an unnamed struct type should be like struct {
var x []int
final y []int.unfixable
z []int.fixable // the ".fixable" can be ommited here,
// just like it in the "x" declaration.
} As we can see, there are three prefix options:
And there are two suffix options:
However, with all of these prefixes and suffixes being supported, the language will become much complicated. So I suggest to only support the prefixes, and (for struct field declarations) let
So, for the type CounterA struct {
var mu sync.Mutex
n uint64
}
func (c *CounterA.fixed) Get() uint64 {
c.mu.Lock() // OK: no problems now
v := c.n
c.mu.Unlock()
return v
} Is it an acceptable design? (Personally, I think this design is a bit ugly, but I haven't found a better one yet.) |
I just re-read this proposal thread. It looks the |
Does anyone think taking addresses of Edit: calling intermediate results as final values may be good, but unaddressable values and final values should be two different concepts. |
Thanks. While there are differences from #22876, this is similar enough that we are going to group them together. They both center around the same idea of qualifying types to mark them as read-only. Both require more thought, and I hope we can combine the ideas into something that will work. |
@ianlancetaylor I do agree the
I also put much effort on improving the syntax. Comparing to the old revisions of this proposal, the current syntax is much simpler and more readable. In fact, this proposal is not just an early-age idea, I have put much effort to evolve it into a full practical solution. Currently, it specifies the detailed rules for almost every corner. And I'm still updating and improving this proposal constantly, almost everyday. This proposal is still not stabilized yet, some new elements are still in pending status. If it should be closed, I really expect another reason, such as there are serious flaws in it. So I appeal to reopen this issue. |
Frankly, this proposal is long and with so many revisions that I find it very difficult to understand. And the fact that you are updating and improving the proposal constantly is not helpful for proposal review. We can't evaluate a proposal that is constantly being changed. If you really think that you need to discuss this separately from #22876 then I recommend that you bring this proposal to a final and complete state that you are happy with, perhaps with discussion on golang-dev or elsewhere with other interested people, and then open a new issue with the final, complete, proposal that we can reasonably examine. That said, I have to say that I am not optimistic about any type qualifier based approach to read-only parameters. Perhaps there is a way to make it work nicely in Go, but as has been discussed in many places I haven't seen one that avoids the problems with the Thanks. |
I can't make a final proposal I'm happy with without collecting feedbacks. Although the feedbacks I got here are also not many, but they really helped me improving the initial proposal much. But OK, I think I'm almost happy with this proposal now (except the struct field part). I will try to post it at the last place, golang-dev., where I might get more feedbacks. I will try to create a new issue again when all are done. |
(The latest version of this proposal is here.)
Please note, the following many comments are based on the initial proposal.
The text was updated successfully, but these errors were encountered: