(Possibly stupid) question about borrows created in function call arguments


#1

Consider something like this (https://play.rust-lang.org/?version=nightly&mode=debug):

struct Foo {}

impl Foo {
    pub fn mut_void(&mut self, n : i32 ) -> () {}
    
    pub fn mut_ret(&mut self) -> i32 { 42 }
}

fn main() {
    let mut foo = Foo{};
    
    // This is okay
    let ret = foo.mut_ret();
    foo.mut_void(ret);
    
    // This is not okay
    foo.mut_void( foo.mut_ret() );
}

The nested calls in the last line of main is not allowed because foo is borrowed as mutable twice. Naively i assumed that borrow in foo.mut_ret() would be “released” by the time foo.mut_void was called because mut_ret has to complete before the call to mut_void can be started.

Is there any particular reason the latter syntax is not allowed, or will it be allowed as part of the NLL changes i read about somewhere?


#2

I believe NLL is supposed to allow this, but it doesn’t appear to at the moment: https://play.rust-lang.org/?gist=0dd9706afc499bb689f358a163682336&version=nightly

Interestingly, NLL does allow this to compile if mut_ret borrows self immutably, which doesn’t work without NLL. But the mutable case still fails to compile - not sure if that’s a known limitation of the current NLL implementation or a bug in it.


#3

At the very least, one needs to be more careful when nesting &mut self calls (as opposed to nesting a &self call within the argument to a &mut self call), because of examples like the first one given in “code we would not accept” in Niko’s blog post:

http://smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/#code-we-would-not-accept

That example is of course a little different than the scenario described by the original poster, since the former example is doing v[0].push_str( { v.push(...); ... }) (where the method receiver in the outer call is the result of evaluating v[0], not just v), while the latter example is using the same receiver (foo) at both points.

But the big idea is that we are initially trying to deploy a very conservative solution to the original v.push(v.len()) problem. We have not yet invested the effort in trying to make a formalism of this change and therefore have little to work with in terms of convincing ourselves that more aggressive changes are always sound. (Its possible, for example, that we could make a special case for when one is using the same local variable as the method receiver at both the outer call and the nested inner one. But the current implementation does not include such a special case scenario.)


#4

Thanks to both of you for replying, good to see how thorough the analysis is of these kind of scenarios.