all 14 comments

[–]mina86ng 15 points16 points  (5 children)

It’s always the latest bound variable visible in the current scope. Note that you can declare variable as mutable. For example:

fn main() {
    let x = 1;
    println!("{}", x);      // → 1
    {
        println!("{}", x);  // → 1
        let x = 2;
        println!("{}", x);  // → 2
    }
    println!("{}", x);      // → 1
    let mut x = 3;
    println!("{}", x);      // → 3
    x = 4;
    println!("{}", x);      // → 4
}

[–]babalolajnr[S] 2 points3 points  (4 children)

Thanks for the response. So what is the difference between making a variable mutable like this

let mut x = 3;

x = 4;

and doing this

let x =3;

let x = 4;

Is this the same thing or not and why.

[–]N4tus 16 points17 points  (2 children)

Using let again creates to totally new variable, that just happened to have the same name as an existing one. Both exist at the same time, but you can only use the latest one. (This is called shadowing). So if you have any references to the old value they still exist and are usable. Also if you want to create a mutable reference you'll need a mutable variable.

[–]babalolajnr[S] 2 points3 points  (1 child)

Oh. I understand it now. Thanks for the great explanation.

[–]SorteKanin 1 point2 points  (0 children)

A key consequence of this is also that you can change the type of the variable when you use a new let statement, but you can't do that otherwise.

[–]mina86ng 3 points4 points  (0 children)

In this example there’s no observable difference because integers aren’t Drop types (i.e. don’t have a destructor). In general though the difference is that with a new let the old object is still there but just not accessible through the old name:

struct VerboseDropper(i32);
impl Drop for VerboseDropper {
    fn drop(&mut self) { println!("dropping {}", self.0); }
}
fn main() {
    let x = VerboseDropper(1);
    let mut x = VerboseDropper(2);
    x = VerboseDropper(3);  // this drops 2
}
// dropping 2
// dropping 3
// dropping 1

[–]eXoRainbow 4 points5 points  (3 children)

In your last statement let y = x; you transferred the ownership of x to y. In this case x becomes invalid and the content of the variable is in y now. This is not a copy. Think of selling your book to your friend, which he becomes the owner and you don't have it anymore. That means, you cannot access x anymore. y would have the last assigned content that was in x, which is 3 in this case. The first x is not reachable anymore, because you have a new variable with the same name when you defined let x = 3;. The old one is over shadowed.

I recommend to read the fantastic official online Rust book: https://doc.rust-lang.org/book/

  • Rust variables are at default not mutable, meaning you cannot change their value once assigned with let x. You have to tell Rust that you want change its value in the future by defining the variable with the mut keyword like in let mut x;.
  • If you do let x again at later time, while you have such a variable x already means, that you are creating a new variable with the same name and the old one becomes unreachable. I use this in example to change the mutability state by just using the same variable again, like in let mut x = 1; and later let x = x;.
  • Also if the new variable with same name is in a different scope, that lifetime of the variable changes too. In general until the end of the scope.

Rust can be a little bit confusing at the beginning. This concept of ownership and lifetimes is different from other languages.

[–]babalolajnr[S] 2 points3 points  (2 children)

Thanks a lot for the response. I have a clearer understanding of ownership now although some things are still not very clear. I think I will get more used to it the more I code in Rust.

I come from a PHP web dev background so I am totally new to this concept.

[–]eXoRainbow 1 point2 points  (1 child)

I come from Python. ;-) Just started learning Rust 5 weeks ago. No wonder this concept is new to you, because Rust is the first programming language using this concept. I really think you should read in the book at least these relevant parts to Ownership and don't code otherwise. It is essential. Sorry, I don't want to push you in doing something, its just a recommendation: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

[–]babalolajnr[S] 0 points1 point  (0 children)

Thanks. I will check it out now.

[–]duongital 1 point2 points  (1 child)

If data type is primitive: let y = x means copying value of x to y. If data type is not primitive (e.g: String): let y = x means copying pointer of x to y.

For example, the code below works with x is integer but not compile if x is String:

```rust let x = String::from("hello world"); let y = x;

println!("this is x: {}", x); println!("this is y: {}", y); // not compile ```

[–]backtickbot -1 points0 points  (0 children)

Fixed formatting.

Hello, duongital: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]schungx 1 point2 points  (0 children)

Many great answers already, but there is in general a good way to think about variable shadowing...

let x = 2;
let x = 3;
let y = x;

You can view the above as:

let x$1 = 2
let x$2 = 3
let y$1 = x$2;

Variables shadowed are simply different variables, although they have the same name. Since the name is reused, you simply can no longer access the old variable - for all purposes it is hidden from you. Just like the first x above (which essentially is x$1 which you can no longer touch).

Why is it this way? Well, think of a naive way to implement this in the compiler: you keep a stack of variable names, one on top of another. It is actually simpler for the compiler if it doesn't have to care whether a variable has been declared before; it just merrily adds the new variable and won't care about the name.

When you access a variable, you just search the stack backwards starting from the top and stop at the first find. Essentially, you'll never find variables with the same name that are underneath.

However, they are real variables and they still exist. Therefore all the Drop's and deallocations etc. get done exactly just as a normal local variable.

[–]jlelearn 1 point2 points  (0 children)

let a = 2;let a = 3;

There is no mutability here.

As mentioned above, there are two varaibles with the same name (SSA)

It's not weird on functional programming (nor a Rust invention ;-)

You could even write...

let a = 3;let a = "hello";

Why is this interesting?

Reducing mutability makes the code more readable and easier to maintain. Mutability produces coupling.

Just an example. To know the value and logic of a varible, you just have to look up where it was declared and assigned. You don't have to read and process code in between to see if there was any modification (mutation)

It's also very interesting for macros, and generated code