vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.56k stars 2.15k forks source link

Immutability of variable may be broken #17135

Open JoanaBLate opened 1 year ago

JoanaBLate commented 1 year ago

Describe the bug

struct MyStruct {
mut:
    list []string
}

fn main() {

    a := MyStruct{ ["aaa"] }

 // a.list[0] = "### MUTATION ###" // error: `a` is immutable, declare it with `mut` to make it mutable

    mut b:= a
    b.list[0] = "### MUTATION ###"  // OK, breaks immutability of 'a'

     println("now first in 'a.list' is ${a.list[0]}")

    // output:
    // now first in 'a.list' is ### MUTATION ### /
}

Expected Behavior

Not mutate the immutable variable 'a'.

Current Behavior

Mutates the immutable variable 'a'.

Reproduction Steps

Run the sample code.

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.3.2 5aad0db

Environment details (OS name and version, etc.)

OS: linux, Linux Mint 20.1 Processor: 8 cpus, 64bit, little endian, Intel(R) Core(TM) i7-3632QM CPU @ 2.20GHz CC version: cc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

JalonSolov commented 1 year ago

Alex agrees this is a bug. See https://github.com/vlang/v/discussions/17122 for the discussion leading up to this issue.

Dairoe commented 1 year ago

this is fine its not the same it just copies if you do mut b := &a it would show you,

But it does look wrong

One way may fix this is not allowing copies so easy one would need to state its a copy.

Sometimes it good to take a step back is it really a bug or a user misdirects, what is doing under the hood?? Is the user trying to reference or copy data ???

JoanaBLate commented 1 year ago

I just corrected what I wrote in the section " Expected Behavior" .

Wajinn commented 1 year ago

Sometimes it good to take a step back is it really a bug or a user misdirects, what is doing under the hood?? Is the user trying to reference or copy data ???

Looks like you are intuitively and correctly picking up as to what is going on. I think that it's being described as a bug, so that corrective actions can be taken, to mitigate confusion or certain expectations. I do think it's a good idea to add more clarity.

I'm of the opinion that the entirety of possibilities or levels of immutability might not be considered. So immutability could possibly be seen at only one level, and not the other. The mutability of the variable is independent from the mutability of the object and is beyond its control, might be ignored.

Example:

Var A is "immutable", but points to Object C which is mutable and outside of the function. Var A being immutable means it can't be changed directly, however Object C is mutable, and it can be changed.

Var A's immutability is independent of Object C. We are dealing with "2 levels of immutability or mutability", which in this case, pertains to variables inside the function and objects outside the function. Because Var A is immutable, and can't be changed directly, but that does not control if Object C is or is not immutable.

Therefore, if we introduce Var B and it's mutable and Object C is also mutable, then Var B can change the contents of Object C.

Var A (despite being immutable) will now show the new value of mutable Object C. Because didn't the user want Object C to be mutable (by using mut)?

If the user doesn't want Object C to be mutable, then they should debatably set it to immutable, then mutable Var B won't be able to change the contents of now immutable Object C (thus MyStruct could be changed to immutable).

But it does look wrong

Something to keep in mind is that rules concerning mutability or const/constants can vary greatly between languages. How JavaScript, Pascal, or Go do things (for example) can be very different from each other. A person can get used to a set of rules or thinking in one language, then attempt to apply them in another language, where it doesn't fit or they feel some odd kind of way.

One way may fix this is not allowing copies so easy one would need to state its a copy

Among the ways to mitigate, in such situations, is to have a notice warning about assignment of a mutable object to an immutable variable. For many, just the notice would likely be fine. Of course the compiler could force that to proceed requires unsafe {}, but that seems quite weird to me. Another possibility is to have a new clone() or copy() function (MyStruct{}.copy()) at the level of struct, to clarify mutability (among other things) and make it the same as the variable it is assigned to. So regardless of what mutability the outside object it refers to was, it now becomes the same as the variable it is assigned to. But don't know if such measures are really necessary. Anyway, whatever team V decides, will probably be well thought out. Such things are tricky.

JoanaBLate commented 1 year ago

Side comment: for those whom might be thinking "What is the use of an immutable variable holding an instance of a struct with mutable fields?":

struct MyStruct {
  mut:
    field1 int
    field2 string
    field3 []string
}

fn main() {

    mut a:= MyStruct{}

    update_field_1(mut a)
    update_field_2(mut a)
    update_field_3(mut a)

    first_model := a // first_model is immutable: I am *freezing* the data

    /*
        bonus: in case I want a second model, slightly different from first_model

        update_field_1_other_way(mut a)

        second_model := a // second_model is immutable: I am *freezing* the data    
    */
}

This is how I write my real life programs.

Wajinn commented 1 year ago

Another way to approach this, is by allowing structs to be declared inside of functions. This was requested previously, but got mixed up with the concept of anonymous structs, which is not quite the same thing. Previous discussions- 14671, So, we can have structs within structs, but people also need structs declared inside the function, and can be used solely in that place and way.

This may eliminate confusion on multiple levels of mutability. Instead of confusion about the mutability of an object outside the function versus the mutability of the variable its assigned to inside the function, the user can declare and assign the struct inside the function. The mutability of the stuct is automatically synchronized with the variable it's assigned to, inside of the function. Then everything is tracked from the variable. Similar to what we can do with associative arrays.

fn foo(){
    mut my_struct := struct {
        field1 int
        field2 string
        field3 []string
        }
}
JoanaBLate commented 1 year ago

{ Another way to approach this, is by allowing structs to be declared inside... }

The basic way is very fine. Declaring the struct inside a function would bloat the function and make the struct inaccessible outside the function.

{ This may eliminate confusion on multiple levels of mutability... }

"multiple levels of mutability" is a confusion by itself and DOES NOT CONFORM TO V.

I feel uncomfortable writing the current message. 3 V team people (including Alex) declared it is a bug. I feel that each new message is just noise.

I will not reply to you anymore on this issue.

Wajinn commented 1 year ago

"multiple levels of mutability" is a confusion by itself and DOES NOT CONFORM TO V.

The confusion could be based on lack of understanding. V allows setting mutability for objects outside of the function, in addition to variables inside the function. Thus we are dealing with independent levels of mutability. The fact that we have this option is a great feature.

The desire to not deal with it, is illogical, unless the language is turned into something completely different or maybe functional programming, which few would likely want. Even then, other kinds of problems present themselves.

...DOES NOT CONFORM TO V

Besides the mean yelling, this doesn't make sense. The way V works presently, are the rules and specification, until they are changed.

The basic way is very fine.

Then that would mean to deal with the different levels of mutability, of inside and outside of the function, and write the program accordingly. There can be many ways to solve a problem. We shouldn't be blind to only one way.

Declaring the struct inside a function would bloat the function and make the struct inaccessible outside the function.

Disagree with that statement. A person can choose to use them or not. So there would be no additional bloat. It would be an option (among other options), that may help various people solve their issues.

The point of objects outside of the function, is that multiple functions can use them. However, if the programmer has a specific requirement or problem, a struct inside of a function (using and used by other functions) could allow them to address their issue.

I feel uncomfortable writing the current message. I feel that each new message is just noise.

I feel equally uncomfortable. The point of discussion and the language itself, is that it would be useful for many people, not a single person trying to force their way. The suggestions made, are for the purpose to spur discussion and debate for what might be better overall. The intent is to also learn and become more knowledgeable.

I will not reply to you.

This is a public forum, not somebody's personal forum. Nobody is forcing anybody else to reply to something. We should be in control of ourselves. When something is written, it's not just a matter of me and you. Others can reply and share their opinion or not, and they can read or discuss in their circles (or here) about what they believe.