70 %

Rust creates a temporary which is freed while still in use

While using std::process::Command to build Toast I ran into this issue:

creates a temporary which is freed while still in use

The simplified code looks like this. We create a new sub-command to run using Command::new, pass a path in (the env, etc on that line isn't important), and add some args to the command. Later, we use the command variable to add another argument. In my case I did this a bunch in a loop but for our purposes we only need one additional cmd usage. The additional .arg isn't even really necessary, we could have cmd; there.

rust
use std::env;
use std::process::Command;
fn main() {
let mut cmd = Command::new(
env::current_dir().unwrap().join("toast-render.js"),
)
.arg("src_dir")
.arg("output_dir");
cmd.arg("another_arg");
}

This results in the temporary value being dropped while borrowed:

error[E0716]: temporary value dropped while borrowed
--> main.rs:5:19
|
5 | let mut cmd = Command::new(env::current_dir().unwrap().join("toast-render.js"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
6 | .arg("src_dir")
7 | .arg("output_dir");
| - temporary value is freed at the end of this statement
8 | cmd.arg("another_arg");
| --- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value

This code is semantically different than the original code, which does the same thing.

use std::env;
use std::process::Command;
fn main() {
let mut cmd = Command::new(
env::current_dir().unwrap().join("toast-render.js"),
);
cmd.arg("src_dir").arg("output_dir");
cmd.arg("another_arg");
}

Explanation

but why? all we did was move the .arg calls to later in the program.

The implementation of Command::new returns a Command while .arg returns a mutable reference to a Command: &mut Command.

This means that what we get back into the cmd variable in the problematic case is different than what we get in the successful compilation. In one case cmd doesn't own the Command, which means that it will drop after we build.

but what is a temporary?

When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead, except if promoted to a static. The drop scope of the temporary is usually the end of the enclosing statement.

Specifically the drop scope of the temporary is usually the end of the enclosing statement. In Why can't I early return in an if statement in Rust? we talked about how ; (loosely speaking) turns an expression into a statement. Therefore the temporary we need (Command) lives through the end of the statement, past the original arg calls, and is then dropped at the end of the statement. We can see this from the compiler help text that specifies the ; as the offending statement.

7 |.arg("output_dir");
| - temporary value is freed at the end of this statement

At this point if we try to use cmd again later in the program, we discover that the temporary was freed (because the compiler tells us) and thus we can't access it when trying to add another arg: cmd.arg("another_arg");.