Explanation: Temporary value dropped while borrowed

Hello guys,

I really try to dig in to the Rust language and I think I need a basic explanation for my current problem.

// A variable i want to change if condition is met
let mut search_path = ".";

// clap command parser match 
if matches.is_present("git") {
    // Get the filename of PathBuf and convert to &str
    let git_path = find_git_root().file_name().unwrap().to_str().unwrap();
    // Here the error happens 
    search_path = git_path;
}

/// Search for nearest .git root folder in parents
fn find_git_root() -> PathBuf {
    match check_parents(3, ".git", Path::new(".")) {
        Ok(path) => path,
        Err(_) => panic!("Could not find a git root folder"),
    }
}
.
.
// search_path later used here
let walker = WalkDir::new(search_path).into_iter();

In general, I think i get how the borrow system works, and I think how to solve this problem is to copy the actual value of git_path to search_path. If this is right, how do I achieve this? If not, what is the idiomatic rust way to do this?

Thanks in advance,
Patrick

1 Like

find_git_root returns a PathBuf, while search_path is a &str. If you look up string slice, you'll find alot of things that explain what &str is. But the error you're getting is that PathBuf is (and cannot be dereferenced to) &str. You note that copying the string is probably the correct solution, but you can get away with just converting it in place:

let mut search_path = ".".to_string();
if ... {
    search_path = git_path.into_os_string().into_string().unwrap();
}
1 Like

So what i learned for now is, that for string manipulation it is better to use String and use &str for (more or less) only viewing add the data. String makes it also easier for dereferencing.

Is that right?

No, not really. Here it's not that you are performing any sort of string manipulation; it's that you are trying to store the string which you get from another place. So you need ownership of the string object.

It's all a matter of whether you are manipulating a String that you own (meaning that you get to dictate when it is dropped, and that it will thus be always available for as long as the variable that holds it is), or whether you are just "manipulating" a view over somebody else's string, in which you case you can restrict / move the view (which can be enough for some "manipulation", such as .trim()ming), but of course your view ceases to be usable when the string you're "just" viewing is moved or dropped.

In your example, the owner comes from find_git_root(). As you can see, the owner is not a variable but a function call: in this case, the value is stored in a temporary anonymous compiler-generated variable which is dropped at the end of the "statement" (i.e., right after let git_path = ...; it has been dropped and no longer exists). So your view is no longer usable.

That's why the first thing to do in these situations is to take matters in hand: instead of this anonymous compiler-generated temporary, let's start with having our own variable to help us track down and control where exactly this owned thing is dropped:

if matches.is_present("git") {
    let git_root = find_git_root();
    let git_path = (&git_root).file_name().unwrap().to_str().unwrap(); // transitively borrows `git_root`
    search_path = git_path; // OK
} // drop(git_root); /* => search_path is no longer usable */
// ...
let walker = WalkDir::new(search_path).into_iter(); // Error, use of `search_path`

This way, even though we still get an error, it has already moved a bit, since we have managed to give git_root a slightly longer life.

But how do we solve the remaining error?
Well, by making git_root live even longer!

To achieve this we need to have the git_root variable be declared outside the scope of that if block, ideally right before the search_path (view) variable is declared, so that each place where search_path is used, git_root is also accessible.

But there is an issue doing that: how can we declare a variable outside a scope where it gets its value?
Easy, we can use the delayed initialization pattern:

let git_root: PathBuf; // no value yet...
let mut search_path = "."; // a view over a constant (and thus ever-lasting) `str`

if matches.is_present("git") {
    git_root = find_git_root(); // just an assignment, no declaration (no let)
    let git_path = (&git_root).file_name().unwrap().to_str().unwrap(); // transitively borrows `git_root`
    search_path = git_path; // git_root not dropped yet: OK
}
// ...
let walker = WalkDir::new(search_path).into_iter(); // git_root not dropped yet: OK

/* At the end of the scope: */
// drop(search_path);
// drop(git_root);

Alternative solution

The above neat trick cannot always be used (in a more complex control flow you will not be able to find a right spot for git_root). In that case, you do need ownership. So search_path, instead of being a &str, i.e., a borrowed view over a slice of valid UTF-8 bytes, ought to be able to own (through a heap allocation) the valid UTF-8 bytes it wants to refer to.

The more straight-forward type for this is String:

// Copy the byte b'.' into a new (owned) heap allocation:
let mut search_path: String = ".".to_owned(); // In C this would be strdup()
// or .to_string() or .into() or String::from(".")
// ...

    let git_path = ... .to_owned();
    // \-> copies the obtained bytes using the temporary view so that we no longer depend on that view

// ...
  • The above can be slightly optimized into not heap-allocating "." by using Cow<'static, str> instead of String, which is a type which does not need to strdup() into the heap when the input str view is 'static (ever-lasting): &'static str (this is the case of all string literals like ".").

  • If you end up needing multiple owners and not manipulating the underlying string, another interesting owning type is Arc<str>, which, in exchange of not letting you mutate the string, offers creating extra (shared) "owners" to the backing string through calls to Arc::clone().


Aside

You can color your code by using triple backquotes:

```
// your code here
```
1 Like

Thank you, for your detailed response.

To recap:

  • The naive approach (while using &str type for search_path) is to ensure that git_root is living as long as search_path is used. (Initialization Patter)
  • String is able to obtain the value (bytes) and is therefore not dependend on any ownership (in this case from the function)
  • There are other concepts or improvements like Cow and Arc which I clearly have to research on my own
  • There is nice coloring

Thank you!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.