Who owns data generated in the middle of a chaining method call? The scope of borrow checker

Hi guys,
I'm thinking of this problem when learning borrow checker:

    let a = vec!{1,2,3};
    let _b=a.clone().iter_mut(); //no complain

In this chained call, clone() returns a new data of vector and iter_mut()'s signature says Iter_mut() just want to get a mutable reference from the new created data. Since rust compiler doesn't complain on iter_mut(), it must successfully borrows mutably from owner of data.
The question is who owns the cloned data?
Is this out of scope of borrower checker? Meaning, borrower checker only applies to code where there is an assignment like let a = … or passing argument to function? If I change code this:

 let a = vec!{1,2,3};
 let b=a.clone();     
 let c=b.iter_mut(); //cannot borrow `b` as mutable as it is not declared as mutable

rust complains as expected.

is it true that if I want to reduce the number of times bothered by borrow checker, I should use chainning call as much as possible?

Every exclusively owned object that isn't currently borrowed is mutable. Every single one.

The let vs let mut is just a shallow "did you mean to do it?" lint, and is not part of language's strict semantics about mutability (unlike & and &mut which are strictly enforced).

Temporary objects get to "cheat" let vs let mut distinction, and are always allowed to be mutated. Technically, because there's no binding for them to state the intent, and the language would be annoying if you had to make a variable for every temporary mutation.

let has_no_mut = vec![1,2,3];
{has_no_mut}.push(1); // legal, every Vec is mutable, and {} moves
let has_no_mut = vec![1,2,3];
let mut has_mut = has_no_mut; // also legal, let without-mut can't stop you

As for ownership, the temporary objects are not owned by anything, and are destroyed at the end of the statement. The iterator borrowing from the temporary clone won't work. The fact that _b is unused allows the borrow checker to ignore the violation. If you try to use _b (e.g. for _ in _b {}), you'll get a borrow checking error.

2 Likes

For context, let's discuss the difference between two things in Rust which look similar, but are quite different.

&T and &mut T are two distinct types[1] with very different semantics. &T is a shared reference which you can copy,[2] whereas &mut T is an exclusive reference which you can't copy (but you can reborrow). It's undefined behavior to have a &mut T and a &T be active at the same time while pointing to the same place, because the &mut T is exclusive -- that's how deeply ingrained the exclusivity of &mut _ is for the language.

In contrast, let a: T = ... and let mut b: T ... does not result in a and b having different types; they're both T. We say a and b are bindings; you can have mut or non-mut bindings. Non-mut bindings restrict what you can do with the variable. Namely, you can't overwrite it, and you can't create a &mut _ to it. If you take a compiling program and change every non-mut binding to a mut binding, you won't change the semantics of your program.[3] You can think of non-mut bindings as a compiler-enforced lint.

Whether a binding is mut or not doesn't change your ability to move values either. For example, you can move a variable between mut and non-mut bindings.

let s1 = "Hello".to_string();
let mut s2 = s1;
s2.push_str(", world!");

So non-mut bindings do not mean "everything about this variable is intrinsically immutable". You can move it to a new mut binding, and there are other ways non-mut bindings can be involved with mutations (e.g. a non-mut binding of a &mut _ reference, atomics and other interior mutability types).


Alright, with that context, let's look at your code.

    // takes a        takes an
    // shared ref     exclusive ref
    //        v       v
    let _b = a.clone().iter_mut();
    //       ^^^^^^^^^
    // A temporary `Vec<i32>`

The temporary returned from a.clone() hasn't been assigned to a non-mut binding, so it doesn't get the lint-like restrictions about not taking a &mut _. So there's no "complaining" about not declaring mut here. There isn't a way to get the lint-like restrictions on temporaries.

There's still a question outstanding: who owns the temporary? We'd have to define "ownership" in some way to answer this well, I suppose. It's easier to answer the question if rephrased: where does the temporary drop? In this case, it drops at the end of the statement. This means that _b is actually not usable -- try to use it and you'll get an error about the temporary dropping too soon.

Sometimes temporaries drop in the surrounding scope instead. Here's a good article on the topic.

I guess you could say that whatever scope the temporary is going to drop in owns the temporary. If you want to be able to change that, you have to bind the value returned from a.clone() into a variable instead (which you could then pass somewhere else).


No, the borrow checker still applies. It just doesn't have a problem with this case.

No, forgetting to use let mut instead of let or having to mechanically change let mut to let to silence warnings are trivialities as far as satisfying borrow check go. Don't let that drive your decisions.


  1. type constructors technically ↩︎

  2. it implements Copy ↩︎

  3. It will still compile and do the same thing, albeit with a bunch of warnings about unneeded mut bindings. ↩︎

1 Like

In terms of the "temporaries drop theory", is it true that the lifetime of temperory vlaue from a.clone() is expaned to each iteration block "{ }" of for loop below?
or placing a.clone.iter_mut() in for loop is allowed because of something else?

for i in a.clone().iter_mut() {
        *i=1;                                                                                                                                                                                                                                                                                 }

why do you place has_no_mut into { }. The overall result is interesting to me. Are you trying to mimic something like function returns? are you trying to say { has_no_mut } returns a temporary object and it's mutable (proved by calling push) to support the statement "Every exclusively owned object that isn't currently borrowed is mutable. Every single one." ?

The for expands to something like

{
    let result = match IntoIterator::into_iter(a.clone().iter_mut()) {
        mut iter => loop {
            match iter.next() {
                None => break,
                Some(i) => {
                    *i = 1;
                },
            };
        },
    }; // <--- temporary drops
    result
}

The statement containing the temporary also contains the entire loop; the temporary drops after the loop at the end of the statement.

It expands in that particular way for the sake of temporaries.

I believe you understand. { thing } moves the thing into the block and then immediately returns it from the block. See also here (but ignore the "Forces References to Move" section).[1]


  1. the compiler is smarter about reborrows than it was in 2015 and the code compiles without the identity function trick ↩︎

1 Like

Thanks kornel and quinedot for your help. I feel both the replies are answers to my question, but quinedot's reply gives me more insights especially from the article. So I choose quinedot as the answer for this post.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.