Bindings to references


#1

Hello!

In one tutorial, I saw something like this:

let mut x = 2; 
let ref1 = &x;      

let ref2 = &mut x; 

This doesn’t work because Rust doesn’t allow
mutable and immutable reference to the same thing in the same scope.

So far so good. But then I started to think about binding.

What if, instead of binding to ref2,
I simply try to make a re-bind of ref1, like this:

let mut x = 2; // no changes here
let ref1 = &x; // no changes here

let ref1 = &mut x; // this doesn’t work, but this completely confused me

I understand I can’t have both immutable and mutable borrow.
But I was somehow expecting re-binding of the ref1 to be more
important than the fact it was holding a reference.
Or (maybe this is the key to understanding): the fact that there was a borrow.

I mean, ref1 is the only borrower.
But this means that ref1 cannot rebind (using let) and borrow x as mutable,
as long as “there is a borrow” - even if one who borrows is the same,
one and only ref1?

This is a bit confusing - or maybe I just need another way to think about this…


#2

This is one of the few “pitfalls” that I know of in Rust. Creating the second ref1 doesn’t invalidate the first one. It will only create a new variable with the same name, and the first one will only become inaccessible. What you get is not

let mut x = 2;
{
    let ref1 = &x;
} //First ref1 is destroyed
{
    let ref1 = &mut x;
} //Second ref1 is destroyed

but rather

let mut x = 2;
{
    let ref1 = &x;
    {
        let ref1 = &mut x;
    } //Second ref1 is destroyed
} //First ref1 is destroyed

where the old ref1 still lives on in the background. You need to get rid of the first borrow and that is easiest done using an explicit scope.

You can however also do things the other way around, by first borrowing mutably and then re-borrowing immutably:

let mut x = 2;
let ref1 = &mut x;
let ref1 = &*ref1; //Dereference ref1 and borrow it again

#3

I could be wrong but (future) non-lexical borrow scopes might solve this.


#4

That’s how I understood it.


#5
let mut x = 1;
{
    let ref1 = &x;
    // ...
}
{
    let ref1 = &mut x;
    // ...
}

#6

Thanks for all the answers. I understand another scope/pair of braces would solve this.

But the real reason I posted this was I thought I misunderstood
something important about bindings and references.

So - it seems this is something
that actually should work but is not there yet.


#7

The lifetime of each variable is currently extended to the end of the scope it was created in, even if you bind a new one with the same name, so they will all be destroyed in the reversed order of creation, but non-lexical borrow scopes are meant to shorten these lifetimes to only cover the part where they are actually used. This will hopefully also apply to your situation (I really think so and I really hope so, but I don’t know for sure). It’s hard to say that it should work, because things are defined in such ways that it doesn’t, but it may work some day in the future.


#8

I see.

Anyway, you wrote something interesting here:

The lifetime of each variable is currently extended to the end of the scope it was created in,
even if you bind a new one with the same name

So I thought I could share my experience while trying to understand these things.

Btw, I think newcomers like me would benefit if the book gets one big chapter dedicated to bindings, with lots of pictures :slight_smile:

I also think this is why I find Rust somewhat hard to learn:
it looks like some important basic things - like bindings - are explained very briefly in the book.

For example, I wasn’t sure what exactly happens when you change something by first declaring it as mutable, compared to what happens when you rebind something - that is, make another binding with the same name.

So I thought printing memory addresses could help:

let mut x=2;
println!("{:p}",&x); // prints 0x7fff30dea794

x=3;
println!("{:p}",&x); // prints 0x7fff30dea794

This was as expected, the address of x was still the same.

But I thought that rebinding works the same way:

let x=2;
let x=3;

And only after this:

let x = 2;
println!("{:p}", &x); // 0x7fff4323e684

let x = 3;
println!("{:p}", &x); // 0x7fff4323e5f4

…I realized what’s going on:
Two locations for the same name.

So, if every binding takes a different memory location, I thought it should have been possible
to reach the first value of x:

let x = 2;
let ref1 = &x;
let x = 3;

println!("{}", ref1); //prints 2

The point is, if I declare a binding, then I take a reference to that, I can use that reference
even after I make a rebind. This was a discovery for me - maybe for somebody else too.

But I guess it all comes from understanding that this:

let x = 2;
let x = 3;

creates values in two memory locations: so the second does not affect the first.


#9

Exactly. Names are only names and not related to location. If the old variable would be destroyed when a new one was bound to the same name, then this would not work:

let x = 2;
let r = &x;
let x = 3; //r would live too long if the first x was destroyed here

This is actually mentioned in Rust by Example, but I guess it would be nice to go more in depth somewhere.


#10

Definitely.

It’s true the term variable shadowing is mentioned there, but it’s not clear (at least I didn’t understand) what it actually means. Probably because I was holding to a model where each variable is attached to specific memory address.

But even if I wasn’t, if you follow a link to wikipedia, it says

“…variable shadowing occurs when a variable declared within a certain scope
(decision block, method, or inner class)
has the same name as a variable declared in an outer scope”.

Two scopes. And here:

let x=2;
let x=3;

I could see only one.

What I’m trying to say is, programming in any language
can be a very frustrating experience. Especially when you don’t have
clear understanding of important terms and concepts, which are sometimes
very language specific.

I admit I like the way the book at http://doc.rust-lang.org is written
(informal style, relaxed tone…) and I learned a lot from it, but - I think
some very important things are missing.

For example, chapter about variable bindings needs a clear definition of binding and what keyword let does, with more examples, especially when it comes to rebinding names.

A simple memory map would be nice.

It shouldn’t take too much work, and it would definitely make this part of Rust easier to learn.
Maybe this looks trivial to more experienced here, but programming is all about having clear understanding of what exactly that line of code is doing.

It should be about creating. It’s just that it’s pretty hard to create if you don’t have a clear understanding of your building blocks.


#11

I’m with you all the way. Some things are simply hard to figure by yourself, and will need an explanation. I would suggest that you file an issue about this to notify the people who write the official guides and documentation. You could also do this to Rust by Example if you want. That’s the best way to make it happen, unless you feel like writing something up, yourself. :smile: