Passing &mut outside of real value scope

Hello!

Move’s borrow checker forbids passing a reference outside of the variable scope in module. This is, of course, is correct. Otherwise this would have been possible:

address 0x1{

module M {
    struct T {}
    public fun create(): &mut T {
        let t = T{};
        &mut t
    }
}
}

But what actually bothers me is the ability to do the same thing within a single function (say script).
I actually can do this:

script {
    use 0x1::M;
    fun main() {
        let a = {
            let t : M::T = M::create();
            &mut t
        };

        M::do_smth(a);
    }
}

Similar case would have been impossible in Rust and it may confuse some developers.
My questions are:

  • how does Move actually work under the hood? why this does not result in error?
  • are you going to change this behaviour?
  • did you leave it on purpose?
3 Likes

Thanks for the question!

I’ve probably overused the word scope in error messages and need to go back and clean it up. To make things clear, here is an overview of the rules of the things you’ve bumped into:

  • Expression blocks ({ e1; ... en} e.g. let a = { let t = ...; &mut t } ) are only there for name spacing. In other words, the only thing they do is add a scope for your lets. They do not add any new rules around references
  • As a whole, we are more permissive than Rust when it comes to references inside of a given function body.
    • Without going into too much detail, Move is only concerned about the relationships between borrows and references. It does not have a lifetime concept like Rust does. This is why your second example works in Move while it doesn’t in Rust.
    • But we are less permissive outside of function bodies because we have no equivalent to lifetime annotations. Meaning there are relationships between the parameters and results of a function that Move cannot express
  • In Move, references returned from a function must borrow from the input parameter references.
    • This is why create does not work in your example.
    • Keep in mind, fun create(t: T): &mut T { &mut t } would also not work since the parameter was not a reference
    • But something like fun ex(t: &mut MyStruct): &mut u64 { &mut t.f } would work

I’ll try to take a look at the error messages here to see how to make them more clear. If you bump into any other errors that you find confusing, please let us know! This sort of feedback is invaluable (as it can be very hard for us to determine what things may or may not be confusing in error messages)

5 Likes

to add some more to @todd 's reply, if we think about bytecodes and function at the VM level there is no concept of scope that blocks introduce. A local in a function has function scope. In that respect your second example does not really define a lifetime for the variable within the function.
Obviously the Move language could choose to be more strict than the bytecode but the value of that is not clear

5 Likes

This post was flagged by the community and is temporarily hidden.