Current:
mut m := sync.WaitGroup{}
a := m // m is duplicated
z := someStruct{wg: m} // m is duplicated and requires &, trap for new players
someFunc(m) // &m is passed
Suggested:
mut m := sync.WaitGroup{} // m = &sync.WaitGroup
a := m // a and m share same data
z := someStruct{wg: m} // z.wg and m share same data
someFunc(m) // &m is passed
I would suggest that by making all muts act as if they were pointers and values otherwise. This would reduce cognitive load and code. The ptr "&" syntax could be eliminated (mostly). This might help towards #41.
Thoughts?
Sounds interesting, I need to think about it.
Just one thing:
fn (foo &Foo) bar() string {
foo is a reference for performance here, but it's not mutable.
Depending on some compiler rule, size of struct say, it can decide whether to use ptr or value. One less thing for dev to think about.
Exactly, that's the plan.
But sometimes people want to make sure that a reference is used.
Perhaps that 1% of cases is not worth complicating the language.
I also want V to be familiar to C/C+/Go devs. Removing pointer syntax entirely may be too radical for them.
Just my opinion. Keeping something that's going to cause errors and newbies to trip up, just so it looks familiar, is a mistake. Personally, I believe many want a rust-safe language, but not as difficult. Otherwise, mission critical stuff will disallow V and non-important may as well stick with simple Go. If you can do both, V is a game changer (more so).
No, I agree, it would be very nice to get rid of * and &.
How would you handle struct fields?
struct V {
mut:
table *Table
}
This is confusing:
struct V {
mut:
table mut Table
}
struct V {
mut:
table Table
}
All mut fields are ptrs or at least work as if they were.
I'd been waiting for generics to implement Bounded Balance Tree in V (see repo). Here I'd need a non-mutable reference:
struct BBTree<K,V> {
// `BBTree` is an opaque immutable type.
left BBTree<K,V> // left subtree; may be nil
right BBTree<K,V> // right subtree; may be nil
size int // the size of the (sub-)tree rooted in this node
key K // the search key; must support the generic ``cmp`` proc
val V // the data value associated with the key, and stored in a node
}
No mut but clearly pointers are needed.
While it is likely the compiler will use a pointer, given that V will have no nil, I think you will just some kind of empty-able type, such as:
struct BBTree<K,V> {
// `BBTree` is an opaque immutable type.
left BBTree<K,V>? // left subtree; may be empty/none
right BBTree<K,V>? // right subtree; may be empty/none
size int // the size of the (sub-)tree rooted in this node
key K // the search key; must support the generic ``cmp`` proc
val V // the data value associated with the key, and stored in a node
}
There are times where it is useful to have that function though, hrmmm.
m := mut sync.WaitGroup{} a := m // m is duplicated z := someStruct{wg: m} // m is duplicated and requires &, trap for new players someFunc(m) // &m is passed
Do you mean mut m := sync.WaitGroup{} or m := mut sync.WaitGroup{}? because if you use the first a := m should be a reference shouldn't it? also i didnt even know that m := mut sync.WaitGroup{} was a thing?
@joe-conigliaro yes I did mean the first and yes it should be a reference (but it isn't yet AFAIK). Corrected original message.
Sorry, my bad! I was thinking you had m := &sync.WaitGroup{} for some reason... in which case it would be a reference.
How would you write this existing V:
x := 1
mut y := x
y++
x is an immutable variable, y makes a copy then increments the copy.
Mutability and referencing are different concepts. V does mix them together for mutable function parameters though.
@ntrel My interpretation of your code is:
x := 1 // Make x = 1, but it cannot be changed (therefore it is passed by value)
mut y := x // Make y the same value as x, so y is set to x's value (1) and y is mutable.
y++ // y becomes 2, x stays as 1
z := y // z becomes an immutable value of 2
y++ // y = 3, x = 1, z = 2
mut a := y // This is where things could go wrong. y and a point to the same value.
y++ // y = 4, a = 4
Maybe we need:
mut a := mut y // for sharing a ptr and
mut a = y // for taking y's value (effectively a copy)
Thanks.
I've created a proposal #1418
@krolaw I'm not a huge fan of mut e := mut b // e is mutable and shares the same data as b
Regarding number 2 maybe something like:
mut e <= b // e is mutable and shares the same data and data type as b which must also be mutable
or maybe even better, mut e <=> b, mut e :=: b, or mut e :: b?
If the shared state is unique it's syntax needs to stand out as the exception and not the rule. But I'd expect the safest thing is just to always make a copy. Any kind of "shared data" should probably stay with concepts of V constants. I can't think of any good examples of variables sharing data (via pointers or otherwise) which is entirely safe.
All mut fields are ptrs or at least work as if they were.
This isn't going to work. V is not Python :)
V needs to be able to work with C/C++ code, and low level (kernel, drivers etc) is one of its main domains.
You can't do that without value struct fields.
mut a := y // This is where things could go wrong. y and a point to the same value.
y++ // y = 4, a = 4
This would be very confusing for users, since it'd be different from every single language where y would be 4, and a would be 3.
mut a := y // This is where things could go wrong. y and a point to the same value.
y++ // y = 4, a = 4This would be very confusing for users, since it'd be different from every single language where y would be 4, and a would be 3.
@medvednikov you're right 100%. But you're quoting a discounted thought process, which I immediately followed up with:
Maybe we need:
mut a := mut y // for sharing a ptr and
mut a = y // for taking y's value (effectively a copy)
Thus, it's consistent with other languages and it's what's reflected in the proposal document.
Which is
Sorry, missed that :) That I'd be okay with.
But the struct fields problem is still unresolved.
@medvednikov
All mut fields are ptrs or at least work as if they were.
This isn't going to work. V is not Python :)
V needs to be able to work with C/C++ code, and low level (kernel, drivers etc) is one of its main domains.
You can't do that without value struct fields.
You're right again. I was trying to explain the idea (poorly). I meant that when mut assigning one mut to another they can be treated as sharing the same memory location - effectively:
mut x = some_struct{} // x := &some_struct{}
mut y = mut x // y := x // y is a ptr pointing to the same data as x
But the compiler could also do it this way:
mut x = some_struct{} // x := some_struct{} (no ptr)
mut y = mut x // y := &x // y is a ptr pointing to the same data as x
Or if x isn't accessed after y is created, the compiler could do:
mut x = some_struct{} // x := some_struct{} (no ptr)
mut y = mut x // y := x // y is a copy of x as it doesn't matter in this case
In any case, I believe we need a higher level construct (beyond ptrs) to prevent races. That doesn't mean you don't have value structs, it's just that you can leave it to the compiler to sort out.
Does that help?
Or if x isn't accessed after y is created, the compiler could also do:
mut x = &some_struct{} // x := some_struct{} (no ptr)
mut y = mut x // y := *x // y is a copy of x as it doesn't matter in this case
Yes, I understand now.
It's similar to my original idea of making the compiler figure out whether copying or referencing is needed in fn (foo Foo) bar() {
Still, there are cases where developers need to be certain that there's a pointer.
@medvednikov Can you give me an example? I can only think of calling a C library function from outsite V. But then the compiler can call &c or *c depending on the requirements of the C function, so I'm at a loss. Point me in the right direction and I'll think very hard about it.
Thanks.
Yes, C interop, but also trees (next, prev *Node), pointers to data (like cur_view *View in Vid). Although the latter would probably work fine with cur_view mut View.
V is a simple and straightforward language, and I feel like this may be too magical. Too much happening behind the scenes.
But I think it can be tried. I had exactly the same idea this spring because I didn't like having pointers, which always complicate things.
@medvednikov I'm honoured you have placated me so far. I'm obviously biased over my own idea, (and think sharing intents is simpler than pointers,) but I don't know of another idea on the table. I would also argue rust is more magical and more complicated but not any safer. I would love my idea to be tried. However, if a better and simpler one is presented I will also be thrilled. I desperately want V to reach it's goal of "no undefined behaviour" like rust, but simpler. AKA, I want a language that is safe, but simple enough for me to use, which is where all this passion is coming from.
Thanks.
When using C interop, you could enforce that mut uses ptr and immutables use values. Therefore devs would have manual control when (and only when) crossing the C boundary. If they want to swap between ptr and non-ptr, it would only be one extra line of code (for each variable). I don't currently think it's necessary, but I've had little experience with C interop, and I feel it would be a very small concession to make this idea more palatable.
Like Rust, C interop isn't safe, and it's not considered a "pure" part of the language. It would also be the same in V.
Yes, C interop, but also trees (
next, prev *Node),
The compiler can see that the next and prev fields are self-references to Node in next, prev Node so pointers are required (otherwise the Node struct would have unbounded size!).
How this works with memory management is another huge consideration. E.g., when a programmer replaces a Node, does the old Node need to be freed? In this case is't pretty obvious, but in your other case (cur_view mut View) not so obvious.
@dcurrie V has no nil. But it will have "none", of which would prevent unbounded growth and I'm not sure why non muts can't be "none". Behind the scenes, muts don't always have to be pointers and immutables don't always have to be values. Mut, non-mut aren't directly translatable to ptr and value. You can still build immutable trees from the leaves down when none arrives, presuming an immutable can be none and I can't see a reason why it shouldn't.
In regards to freeing, I'm hoping for auto freeing if internal ptr, and auto calling of destruct either way if method exists, otherwise it can just overwrite.
@krolaw
V has no nil. But it will have "none", of which would prevent unbounded growth and I'm not sure why non muts can't be "none".
Sure. What I meant by "unbounded size" is that assuming next, prev are self-referential (inside a Node and referring to a Node) and if next, prev were implemented as inline structs instead of pointers, the containing struct would recursively grow. Of course you could stop this growth with a "none" value, but the compiler doesn't know until runtime when the recursion will end, so it doesn't know how big to make the Node. The obvious way to break this data recursion is to use a pointer, and it's so obvious it isn't really necessary to tell the compiler to do so.
can still build immutable trees from the leaves down when none arrives
You can build immutable trees from the root down! See my Nim implementation
@krolaw I'll implement this this week. I think it can work.
@medvednikov Awesome :-)
@dcurrie Wouldn't an immutable tree branch be:
struct branch {
left, right branch?
}
It's the question mark that gives the none/nil pointer, not the mut I believe.
It's ?branch, meaning Option<branch>
I'm not sure all algorithms can have optionals instead of pointers. So there will probably be unsafe with pure C style pointers.
Makes sense to me. You can't make unsafe code suddenly safe.
Thanks.
On Mon, 5 Aug 2019, 13:44 Alexander Medvednikov, notifications@github.com
wrote:
It's ?branch, meaning Option
I'm not sure all algorithms can have optionals instead of pointers. So
there will probably be unsafe with pure C style pointers.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vlang/v/issues/1379?email_source=notifications&email_token=AAZKBUWY22R4VE2X3XLMVCDQC6AWBA5CNFSM4IH4VO72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3QOYZY#issuecomment-518057063,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZKBUSDUQ6EU5T3QCHNACTQC6AWBANCNFSM4IH4VO7Q
.
@krolaw
mut a := 1
mut b := &a
*b = 10
becomes
mut a := 1
mut b := mut a
b = 10
?
@medvednikov Yes :-)
@ntrel what do you think about this one? https://github.com/vlang/v/issues/1379#issuecomment-518075842
@krolaw
mut a := 1 can be read as "mutable variable a with an initial value of 1`
fn (foo mut Foo) - "mutable receiver foo of type Foo".
How would you read mut b := mut a?
b points to the same memory location as a. Essentially a is b under a different name. This is the same logic as when a function mutable receiver points to its calling mutable variable. However b can be redirected to point to c, so future changes affect c instead of b.
mut a := 1
mut b := mut a // could be b := &a if a isn't a ptr, b := a otherwise
mut c := 2
b = c // could be b := &c if c isn't a ptr, b := c otherwise
Does that help?
What I meant is, every instruction means something and it can be read out loud by the programmer.
mut a := 1 = "declare a mutable variable a with an initial value of 1"
fn (foo mut Foo) - "mutable receiver foo of type Foo".
How would you read mut b := mut a?
Sorry for the delay. It's tricky to describe in a few words.
mut b := mut a = "declare a mutable variable to share the same memory as
mutable variable a"
Hopefully that's ok.
On Mon, 5 Aug 2019 at 18:30, Alexander Medvednikov notifications@github.com
wrote:
What I meant is, every instruction means something and it can be read out
loud by the programmer.mut a := 1 = "declare a mutable variable a with an initial value of 1"
fn (foo mut Foo) - "mutable receiver foo of type Foo".
How would you read mut b := mut a?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vlang/v/issues/1379?email_source=notifications&email_token=AAZKBURARG3T3KTW7Q6T5IDQC7CG3A5CNFSM4IH4VO72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3Q2ENI#issuecomment-518103605,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZKBURF7S2IZMS3MCOTTWDQC7CG3ANCNFSM4IH4VO7Q
.
--
Richard Warburton - MSc(Hons), PGDipSci, BE(Hons) -
http://richard.warburton.it/
+642-176-5837 - PO Box 77039, Mt Albert, Auckland 1350, New Zealand
I think it could also be "declare a mutable variable b that mutates a".
The reason I'm asking, is the language needs to be readable, and to make sense.
Several people I presented this to asked what the second mut means and why 2 different muts mean 2 completely different things.
If we read the second mut as "mutate", then it makes sense, I think. Just like in update_array(mut numbers).
@medvednikov I like it :-)
To avoid confusion and not have one key with 2 different functionalities, what do you think about introducing a new key for declarations?
var b := mut a
I'm not sure it's a good idea. var is very commonly used as a variable declaration in other languages. If only one mut can be kept, I'd prefer keeping the one on the left, so it's explicit that the var is mut.
mut x := 3
mut y := x // y is mutable but copy of a
mut z := mut x // mutable z mutates x (my favourite)
var a := mut x // my least favouite
mut b := &x // dislike (and not completely accurate) but may appease newcomers from ptr languages
mut c := ref x // dislike
@Compro-Prasad suggested the last one which I dismissed (perhaps too harshly) as it wasn't always a reference (but that may be pedantic), was only used with mut variables and thus we're adding a another keyword when mut will do.
I honestly think mut b := mut a is ok. Because it functions differently to just ptrs and references, I think a different syntax is best and highlights the difference. Plus it fits your previous description beautifully.
However, it's only syntax and I care more about what it does than how it looks.
Thanks for asking.
No! Please no new declaration keywords like var.
But I agree very much that mut has to mean just one thing.
I would prefer mut a := ptr b or mut a := ref b then.
Please do not rush and think it over very well.
Mutability and references are not the same thing, so probably you should not even try to "eliminate pointers" as the heading of this PR says. Maybe it's even not so wise to try this? :)
It's just a different level of abstraction. V already removes the need to think about using pointers vs values in some cases:
fn (foo Foo) bar() { // Foo can be a pointer if the struct is too big
fn (foo mut Foo) bar() { // foo is always a pointer, but the user only specified mutability
modify_array(mut numbers) // an address of numbers is always passed
When raw pointers are needed, they can be used with unsafe.
Wouldn't an immutable tree branch be:
struct branch {
left, right ?branch
}It's the question mark that gives the none/nil pointer, not the
mutI believe.
Yes, thank you. I still wonder how the unreachable parts of the tree will be freed, though.
Let me put my "two cents" here.
1.- I think the main idea should be that everything is passed by value (copied) in V unless you specifically indicate that you need one variable be a reference to another. (This is mostly as it is now, I suppose).
2.- You should never be able to change non-mutable variable via any reference, even if the reference is mutable.
a := 1 // immutable
b := a // copy, both are immutable
mut c := 2 // mutable
mut d := a // copy, `d` can change, but not `a`
mut e := c // copy, both can change, but are separate variables
f := &a // reference, but both are immutable
mut g := &c // both are mutable and share same memory address
mut h := &a // ERROR, cannot get `mut` reference to immutable; currently this is allowed!
Main thing is - if I made a variable immutable, it can NEVER change, no "tools" to change it should be available in the language. If you want to change it, make a mutable copy mut d := a and do with d whatever you want.
If you do not like the & and * syntax (as this is not "friendly" to newcomers and errors like mut g := &c; g += 1 instead of *g += 1 could be quite common), then introduce the syntax like:
mut g := ref c;
g += 1 // `g` is a reference (synonym of `c`), not a pointer, so no dereferencing needed
Same principals should be applied when passing the arguments to functions - cannot pass reference of immutable to mutable argument:
a := 1
fn f(x mut int)
fn g(x int)
f(a) // ok, copy passed
g(ref a) // ok, passed as reference, but still immutable
f(ref a) // ERROR, cannot pass reference of immutable to mutable
Of course, all this should be the same with struct and array.
For C interop, references could be passed as pointers under the hood.
In general, I don't care how compiler uses references/pointers for optimisation, providing that the logic of immutability is not violated. You can "hint" compiler that you need a read-only reference (like fn (x ref Structure) myfn()) of mutable reference (fn (x mut ref Structure) myfn()), but compiler could decide what is optimal.
But the idea of "when mut assigning one mut to another they can be treated as sharing the same memory location" does not sound good for me (sorry @krolaw). I think mutability and references are different things and should be kept separate.
I think mutability and references are different concepts and it would be confusing to mix the two. Having local references instead of pointers is fine, but let's use standard syntax for that:
mut v := 5
ref r := v
r++
r is not declared as mut r := because references bind to an (implicit) address on initialisation, that address never changes. You can require := mut v to emphasise that v can be mutated through r, technically that's not necessary.
@ntrel, I think it would be good to distinguish between read-write and read-only references:
mut v := 5
r := ref v
r = 10 // error
mut m := ref v
m = 10 // ok, both `m` and `v` change
This way you could safely and efficiently pass read-only references even to mutable objects without a risk they being changed by the receiver.
@avitkauskas and all,
Extending your example to a struct (or any other non-atomic type)
struct KVPair {
key string
mut:
val int
}
mut v := mut KVPair{"foo",1}
r := ref v
r = mut KVPair{"bar",2} // error because r is not mutable
mut m := ref v
m = mut KVPair{"baz",3} // does v now refer to {"baz",3}?
In general these proposed rules need to be considered in light of non-atomic values.
it would be good to distinguish between read-write and read-only references
Edit: I think reference mutability has to be based on the mutability of the expression used to initialise the reference.
I don't think V can support a reference to immutable data that is initialised from a mutable variable. This is because a reference to immutable data can be shared across threads. If the data is actually mutable, then the reference is not pointing at immutable data, it's just a read only reference. Supporting that would complicate V's type system (but has benefits).
@avitkauskas and all,
Extending your example to a
struct(or any other non-atomic type)struct KVPair { key string mut: val int } mut v := mut KVPair{"foo",1} r := ref v r = mut KVPair{"bar",2} // error because r is not mutable mut m := ref v m = mut KVPair{"baz",3} // does v now refer to {"baz",3}?In general these proposed rules need to be considered in light of non-atomic values.
This is a nice example. The V compiler could throw an error if such a case occurs. V will have to track the references and throw an error if any one of them tries to mutate the value when there are immutable references in the scope.
Below v and m are a value and reference, yet they're introduced in the same way:
mut v := 5
mut m := ref v
'mutable m from a reference to v'. Not very clear. Instead, let's declare references with the ref keyword:
ref m := mut v
'reference m to mutable v'.
@dcurrie with the correct _current_ syntax it is like this:
struct KVPair {
key string
mut:
val int
}
mut v := KVPair{'foo',1}
r := &v
r = KVPair{'bar',2} // error because r is not mutable
mut m := &v
*m = KVPair{'baz',3} // yes, both `v` and `m` now refer to {'baz',3}
With the new syntax this would be:
mut v := KVPair{'foo',1}
r := ref v
r = KVPair{'bar',2} // error because r is not mutable
mut m := ref v
m = KVPair{'baz',3} // yes, both `v` and `m` now refer to {'baz',3}
@ntrel, yes it's a very good point about the question what should happen if mutable variable having immutable references changes. This has to be prevented for multithreading as I understand.
On the other side, if we take that the mutability of the reference depends on the mutability of the object it points to, then I would like a little modification of your proposal:
mut v := 5
ref m := v
It's clear that m is a reference and its mutable because v is mutable, we should probably not repeat ourselves here. Then, taking @dcurrie code as example we would have:
v := KVPair{'foo',1}
ref r := v
r = KVPair{'bar',2} // error because `r` and `v` are not mutable
mut o := KVPair{'foo',1}
ref m := o
m = KVPair{'baz',3} // yes, both `o` and `m` now refer to {'baz',3}
I think this it is a mistake to lead with ref. Consider:
mut x := SomeStruct{}
mut y := SomeStruct{}
.... // later
y = mut x // mut magically turns into ref
And
mut x := 5
ref y := x
.... // later
y = SomeStruct{} // Hang on wasn't y a ref? ref magically turns into mut
Here y didn't start life as a ref but it's perfectly ok. Wouldn't this cause confusion? And why put constraints on objects that aren't really there? If you think of muts as objects using ptrs (this is purely for understanding, it's not always the case) the difference between mut and ref don't exist.
Therefore while I can concede:
mut x := 5
mut y := ref x // mutable y mutates x
mut z := x // mutable z receives copy of x
I don't believe leading with ref works. It either causes confusion or unnecessarily adds a constraint to the language.
That's a good point @krolaw.
mut x := 5
mut y := ref x // mutable y mutates x
mut z := x // mutable z receives copy of x
This looks readable and consistent.
I guess the struct field would be
struct Foo {
x ref int
}
So what we've done here is simply replace * and & with ref.
No, and this is why I dislike ref. In your struct x can be a mutable value. It can be 5. Ref implies it must point to an existing variable. That's not the case.
Consider:
struct Foo {
x mut int // x can change
}
mut b := 7
mut x := Foo{x: b} // confusion if using ref and want a copy of the value of b
mut y := Foo{x: 5} // confusion if using ref in struct above
mut z := Foo{x: ref b} // again would prefer mut here but concede it still works
muts don't have to be ptrs. muts can contain values.
It's pretty hard for the compiler to detect whether a mutable field is a pointer or a value.
And they are very, very different.
If you have a pointer, you need to allocate memory, and modifying it can modify something else.
If you have a value field, it's always clear what it does, and that only it will be modified.
I think it's actually easier and more readable this way.
Ref implies it must point to an existing variable. That's not the case.
If you mean pointing to a newly allocated struct, we can have
new_user = ref User{name: 'Bob'}
This will allocate a User object on the heap, just like this current syntax:
new_user = &User{name: 'Bob'}
Here's an example:
struct Foo {
mut:
a A
b ref B
}
Is a a reference? How will memory be allocated? Will it modify another object? etc
I had thought that the compiler would start with making all muts ptrs and optimise them away if they don't share values. Essentially, if you don't have a reference allocation in the var's scope it's a value, but that in itself might be too demanding.
Ok, so we have muts (mutable values) and refs (mutable pointers to mutable values).
a := 5
mut b := 6
ref c := b
c = a // sets b to 5
c = 3 // sets b to 3
ref d := 5 // disallowed, should be a mut? <-- this is key
mut e := 7
c = ref e // switch ptr location to e
Just note the key part, because if you don't disallow it, ref can be used in place of mut and then you may as well not have mut and you end up trying to optimise the ptrs away anyway. There should only be one way to do things? If so, refs shouldn't be able to do double duty. If they can then we can dump muts and rename refs to muts, and we're back where we started.
At some stage, you'll be looking at whether you can remove ptrs from certain references depending on how they are used in some cases for maximum speed. When that happens, the point for having refs and muts disappears and we are left with a language construct that no longer has any value, but is locked in V's history.
ref c := b
You said yourself:
I think this it is a mistake to lead with ref.
If we have ref, the syntax will be:
mut c := ref b
struct Foo {
mut:
a int
b ref int
}
mut z := Foo{a:1,b:2} // allowed?
// Or would it be:
mut z := Foo{a:1,b: ref 2}
If mut z := Foo{a:1,b:2} is allowed ref muts can do double duty as std muts. But even if it is blocked I can imagine some cases where one can optimise away b's ptr for max speed. If that optimisation comes in, the difference between ref and non-ref becomes non-existent and one could well presume all muts are refs and apply optimisation.
Maybe computers aren't fast enough yet. Maybe it's too complicated. Maybe we need to differentiate at this time. Maybe this is why the rust compiler is comparatively slow.
mut z := Foo{a:1,b: ref 2} would be required.
I guess my issue is that refs can still do double duty as muts, and it is computational possible to deduce if a ptr should be used. I appreciate it may not be practical at this point in time and could be solved later with vfix.
If muts and refs are different, how do we dereference for passing to functions or as values? Or does this happen automatically.
mut a = ref 8
mut b = a // is this 8 or a? suggest 8
mut c = ref b // is this a or &a ? suggest a
mut d = ref ref b // is this allowed?
And presuming functions:
fn z(a mut int, b ref int, c int) {
So a receives a copy of the int regardless whether it came from a ref, and this copy is mutable.
And b receives a reference to a mutable int that it can change.
And c might receive a reference or not, but it doesn't matter as c can't change.
mut a = ref 8 mut b = a // is this 8 or a? suggest 8 mut c = ref b // is this a or &a ? suggest a mut d = ref ref b // is this allowed?
mut a = ref 8
mut b = a // No ref is used. So dereference and copy automatically.
mut c = ref b // c refers b's memory location. b is just a value.
mut d = ref ref b // I don't understand the use of this thing
fn z(a mut int, b ref int, c int) {
I don't want to change anything about functions. I really like current syntax:
fn modify(numbers mut []int) { .. }
modify(mut numbers)
But if the data is being modified, to be consistent with structs, shouldn't we use ref? Mut would make sense if we are dereferencing and making a copy for editing internally. Otherwise, it's not consistent?
mut means the argument will be modified. That's it.
fn modify(v mut int, z int) {
mut y := z // extra step required if we want a mutable copy to work on
y++
if y > 1 {
v++
}
}
mut b := 1
modify(b) // so here detect b is not a ref and auto ref it
mut c := ref b
modify(c) // and here we detect c is already a ref and pass it
Consider
fn modify(v ref int, z mut int) {
z++ // original not affected
if z > 1 {
v++ // original incremented
}
}
mut b := 1
modify(ref b, 1) // matches fn
mut c := ref b
mut d := 2
modify(c,d) // also matches
Absolutely not an option.
It must always be clear that the variable is going to be modified: modify(mut a).
Ah, I missed the mut in the call. Thinking.
mut b := 1
mut c := ref b
modify(mut c)
Nice so, modify can change b's value and replace c's reference with another. Cool and thanks.
In the end I'm leaning towards your initial idea of making the compiler figure out whether a reference should be used. But still leave a way to force referencing with & (not ref). Just like it already does in fn (foo &Foo) bar() {.
A tree would thus be
struct Node {
....
left &Node
right &node
}
Given V's one way to do things mandate, what's wrong with:
struct Node {
...
left ?Node
right ?Node
}
Why force reference when the compiler can figure it out? Even crossing the C boundary can be solved by examining the declaration.
@krolaw an optional Node is a value type, what if it's a field of another struct Tree - it needs to be an optional pointer.
I think this it is a mistake to lead with ref. Consider:
mut x := SomeStruct{}
mut y := SomeStruct{}
.... // later
y = mut x // mut magically turns into ref
What does that last line mean? y is a mutable struct instance. (y = x would copy x into y).
mut x := 5
ref y := x
.... // later
y = SomeStruct{} // Hang on wasn't y a ref? ref magically turns into mut
???
y is a reference to a mutable int, you can only assign an int to y. (References can't be rebound).
@ntrel I was a bit confused then. I didn't see the issue of muts pointing to values and then to other muts. While illegal in C, might be fine in V. I'm hoping @medvednikov will clarify exactly what has been decided :-)
Hi everybody,
Posting here, because seems its most active thread about pointers.
I think rust approach will fit pretty well here.
We can have following scenarios:
//a value is immutable
a := 1
//a value is mutable
mut a := 1
//a value (reference) is immutable
a := &1
//a value (reference) is mutable
a := &mut 1
//a value and reference is mutable`
mut a := &mut 1
// mutable **value** of a can be changed.
mut a := 5
a = 6
a := 5
b := &mut a
//b value is *mutable reference* to a. Dereferencing is explicit so we always knows that dealing with reference.
// prints 6
*b = 6
md5-2e2648c5590e9ac21326b8d0d39d293e
a = 5
c = 10
// b value is reference which can be changed, but value referenced to can't/
mut b := &a
//changing reference
b = &10
//error: can't change referenced value
*b = 10
md5-2e2648c5590e9ac21326b8d0d39d293e
struct Example {
field int
}
//the only exception should be made for method receiver value.
// fn (mut e Example) mutable_copy() should be illegal (but can be interesting, because with that ability you can change method receiver dynamically.
fn (e mut Example) mutable_copy() {
//e - local, mutable copy
e.field := 10
println("e.field")
}
md5-2e2648c5590e9ac21326b8d0d39d293e
fn (e &mut Example) mutable_reference() {
//e - local, mutable reference
*e.field := 10
println("*e.field")
}
md5-2e2648c5590e9ac21326b8d0d39d293e
fn (e &Example) immutable_reference() {
//error: e - immutable reference
*e.field := 10
println("*e.field")
}
md5-2e2648c5590e9ac21326b8d0d39d293e
fn (e Example) immutable_value() {
//error: e - immutable value
e.field := 10
println("e.field")
}
md5-26ffba1605a1dd7c456be231eb73cd3e
struct Fetcher {
mu sync.Mutex
ids []int
cursor int
wg &sync.WaitGroup
}
fn fetch(x &mut fetcher) {
// can mutate fetcher
}
fn fetch(x &fetcher) {
// can't
}
fn fetch(mut x fetcher) {
//can mutate **local** copy of x
}
fn fetch(x fetcher) {
//can't mutate **local** copy of x
}
Hello,
With what @se16n has shown and what @avitkauskas has pointed out, ref is the keyword alias of & and &mut where the compiler is doing the same intelligent reasoning about what to do based on usage like @krolaw has pointed out. It honestly seems that most of what has been discussed if reread confirms that too. I have not seen a scenario here where mut a = ref b is bad. Something like mut d = ref 4 should be a compiler error (we don't reference non-allocated literals right?).
It is not clear that mut y = 5 and then mut x = mut y have intent to share or not because mut on the right is not and alias for &. I just like simple things and readable code. Best of luck. Thank you. Good day.
Most helpful comment
Just my opinion. Keeping something that's going to cause errors and newbies to trip up, just so it looks familiar, is a mistake. Personally, I believe many want a rust-safe language, but not as difficult. Otherwise, mission critical stuff will disallow V and non-important may as well stick with simple Go. If you can do both, V is a game changer (more so).