2

I've encountered a simple task: I want to pass a sum of strings to a constructor of an object. In my case, I wanted to create a Path from a sum of &str, like this:

const BASE_PATH: &str = "/some/path/";

fn main() {
    let write_br_path = Path::new(BASE_PATH + "brightness");
}

However, I soon found out that I can't sum &str and &str, so, as my compiler suggested, I did something like this:

let write_br_path = Path::new(&(BASE_PATH.to_owned() + "brightness"));

However, this also didn't work. This code failed to compile, informing me that "creates a temporary which is freed while still in use". I've come up with a simple solution:

let m_b = BASE_PATH.to_owned() + "max_brightness";
let max_br_path = Path::new(&(m_b));

This leaves me with a question: can this code be written in a more compact way, without additional variable being created?

keddad
  • 1,398
  • 3
  • 14
  • 35
  • 2
    Use `PathBuf` instead? – justinas Jul 18 '21 at 20:47
  • @justinas well, such a problem can occur with pretty much anything, not only paths. I'm looking for a more general solution. However, looks like `PathBuf` solves this case, thanks! – keddad Jul 18 '21 at 21:00
  • I would advise reading answers to similar problems, e.g. [this one](https://stackoverflow.com/questions/54056268/temporary-value-is-freed-at-the-end-of-this-statement), or more resources on what references/borrows are in Rust. `Path` and `PathBuf` have a smilar relationship as `str` and `String` have. – justinas Jul 18 '21 at 21:12
  • @biryulin04 the general solution would be to make an additional variable because you can't make a temporary object live longer than the current statement without it. – kmdreko Jul 19 '21 at 00:54
  • `Path::new()` is peculiar and may not be a good example on the usefulness of the various idioms for string catenation. You can see the answer I posted for details on why. – Todd Jul 19 '21 at 03:35

2 Answers2

2

To answer this question as it relates to Path::new() specifically, you invoke Path::new() where you need it without assigning it to a variable.

For instance, to pass a Path reference to another function, or macro, such as println!(), invoke Path::new() in the parameter list:

println!("{:?}", Path::new(&[BASE_PATH, "brightness"].concat()));

^^ this works because neither Path::new(), nor .concat()'s return value is being bound to a variable. They're both used once in place.

Although the same code worked when passed to println!() in the example above, the same code in the following example doesn't work because the return value of Path::new() has a let binding, but the actual data it depends on doesn't:

let p = Path::new(&[BASE_PATH, "brightness"].concat()); // Error...

The reason for the need to assign to a new variable is peculiar to Path::new() since it's a 0-cost conversion operation. It doesn't otherwise do anything with the value passed to it, and it returns a reference rather than a concrete object.

From doc on Path::new():

pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path

Directly wraps a string slice as a Path slice. This is a cost-free conversion.

If you're going to bind the return value of Path::new() to a variable, the actual data it depends on needs to be a static string or a string also bound to a variable.

There are a lot of ways to combine strings. This page gives a number of options and has benchmark figures on the performance of each.

Todd
  • 4,669
  • 1
  • 22
  • 30
1

How do I fix “creates a temporary which is freed while still in use” without creating additional variable?

You can refactor your code into a match where the variable is bound via an arm:

match Path::new(&(BASE_PATH.to_owned() + "brightness")) {
    write_br_path => {
        println!("{:?}", write_br_path);
    }
};

Since it is part of the match expression, the temporary created by BASE_PATH.to_owned() + "brightness" is not dropped until the end of it, meaning you can continue to use write_br_path within the arm without a problem.

I do not actually suggest doing this.

You need an additional variable because of the rules for temporary scopes.

This is probably not as compact as you'd like, but a fairly common thing to do is to make an additional variable and shadow it:

let max_br_path = BASE_PATH.to_owned() + "max_brightness";
let max_br_path = Path::new(&max_br_path);

This can be nice to avoid misusing the original variable and enforce use of its shadow. This is mentioned in the rust book in the guessing game tutorial: "This feature is often used in situations in which you want to convert a value from one type to another type."

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • But why does shadowing help here? If I get it correctly, `Path::new` doesn't copy the string passed - it just saves the reference. Doesn't that mean that when `max_br_path` is shadowed and the string it was storing gets freed the reference saved in `Path` object points to nothing? I'm new to Rust, so I have some problems understanding the memory model. – keddad Jul 19 '21 at 08:18
  • Shadowing doesn't necessarily help much, just keeps you from having multiple (potentially confusing) variable names polluting the function. *"is shadowed and the string it was storing gets freed"* - shadowing a variable doesn't destroy it, it will be dropped at the end of the function. – kmdreko Jul 19 '21 at 14:53