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.
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 use6 | .arg("src_dir")7 | .arg("output_dir");| - temporary value is freed at the end of this statement8 | 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");}
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");
.