Understanding cannot move out of `something` because it is borrowed

Hi,

I'm having trouble understanding the error for the following code example (using the dependency git2 = "0.14.2"):

use git2::{Object, Reference, Repository};
use std::path::Path;

fn branch_object_and_reference<'a>(
    repository: &'a Repository,
    branch: &str,
) -> Result<(Object<'a>, Reference<'a>), git2::Error> {
    let branch_name = format!("origin/{}", branch);

    let branch_reference = repository
        .find_branch(&branch_name, git2::BranchType::Remote)?
        .into_reference();
    let branch_object = branch_reference.peel(git2::ObjectType::Any)?;

    Ok((branch_object, branch_reference))
}

pub fn clone_checkout_git_revision(
    url: &str,
    checkout_folder_path: &Path,
    revision: &str,
) -> Result<Repository, git2::Error> {
    let repository = Repository::clone(url, checkout_folder_path)?;

    let (revision_object, revision_reference) = if let Ok((branch_object, branch_reference)) =
        branch_object_and_reference(&repository, revision)
    {
        // Checkout the branch the revision points to.
        (branch_object, Some(branch_reference))
    } else {
        // Checkout the revision hash.
        repository.revparse_ext(revision)?
    };

    repository.checkout_tree(&revision_object, None)?;

    match revision_reference {
        Some(ref reference) => {
            if let Some(reference_name) = reference.name() {
                repository.set_head(reference_name)?
            } else {
                repository.set_head_detached(revision_object.id())?
            }
        }

        None => repository.set_head_detached(revision_object.id())?,
    }

    Ok(repository)
}

This gives the error:

error[E0505]: cannot move out of `repository` because it is borrowed
  --> src/lib.rs:49:8
   |
26 |         branch_object_and_reference(&repository, revision)
   |                                     ----------- borrow of `repository` occurs here
...
49 |     Ok(repository)
   |        ^^^^^^^^^^ move out of `repository` occurs here
50 | }
   | - borrow might be used here, when `revision_object` is dropped and runs the `Drop` code for type `git2::Object`

Looking around on the internet the suggested fixes for this are to either use a local scope or manual drops. Sure enough adding manual drops at the end fixes this:

    drop(revision_object);
    drop(revision_reference);
    Ok(repository)

But I don't understand why this fixes the error. So I basically have 2 questions:

  1. What exactly is the reason for the error in the first place?
    My current working theory is that it's a lifetime issue based on the error message.
    The branch_object_and_reference() function declares that the returning Object and Reference have the same lifetime as the passed &Repository. So when the variables inside the clone_checkout_git_revision() function are no longer used, they get dropped. And since they need to have the same lifetime as the repository variable, that needs to get dropped as well before I can return it.
    Is this somewhat close to what's happening or am I already way off here?
  2. Why do the manual drops fix this error?
    Assuming the above assumption is somewhat correct: Shouldn't manual drops have the same issue? The variables get dropped and since their lifetime needs to be same as the repository variable, the same error should occur?
    Obviously the error doesn't appear so I'm getting something wrong here.

I would really like to understand this issue as it's confusing me quite a bit.

Thanks in advance to everyone helping me understand this!

I'm on mobile, so this might be a shorter answer. Evaluating the return value Ok(repository) moves repository. Implicit dropping of local variables happens after evaluation of the return value.

But dropping those local variables that can contain references to repository must not happen after repository has been moved (which invalidates the references) because the destructor of those variables could still access the target of those references. The compiler will actually differentiate between types that have custom destructors (i. e. Drop implementations) and those that don't [1], I haven't confirmed this but apparently at least one of the types Object or Reference must have a custom destructor (or contain a field that does). [2]

Either approach, the manual drops or introducing a local scope (i. e. a block) that starts after the let repository line and ends before the Ok(repository) will fix the problem because it makes it so that the local variables in question are dropped before the return value is evaluated.


  1. the full logic of this “drop check” is actually somewhat more complicated ↩ī¸Ž

  2. For comparison, e. g. a let foo = (&repository, 42); won't cause any problems. ↩ī¸Ž

4 Likes

Thanks a lot for the explanation! This was quite illuminating.