I have a shell command that I want to initiate, which requires a different invocation on Windows vs Linux. However the arguments are the same—just the initial shell invocation will differ across platforms. According to the Command reference documentation, I would do this:
use std::process::Command;
let output = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(["/C", "echo hello"])
.output()
.expect("failed to execute process")
} else {
Command::new("sh")
.arg("-c")
.arg("echo hello")
.output()
.expect("failed to execute process")
};
I am extremely experienced in other programming languages, and to me that seems like a lot of confusing clutter and redundancy. I would like to consolidate the parts that are similar, and only check the platform in order to create the initial shell invocation pat. I would do something like this in Java:
var cmd = cfg!(target_os = "windows")
? new Command("cmd").args(["/C"])
: new Command("sh").arg("-c")
var output = cmd.arg("echo hello")
.output()
.expect("failed to execute process");
I tried this in Rust:
…
let cmd = if cfg!(target_os = "windows")
{ std::process::Command::new("cmd").args(["/C"]) }
else { std::process::Command::new("sh").arg("-c") };
…
I get the infamous:
error[E0716]: temporary value dropped while borrowed
The same happens using match
:
…
let cmd = match cfg!(target_os = "windows") {
true => std::process::Command::new("cmd").args(["/C"]),
false => std::process::Command::new("sh").arg("-c");
};
…
(Frankly I'd like to match
on target_os
, but I haven't yet had time to research how to do that.)
I get that Rust is creating a temporary variable on each branch/arm. But Rust also knows that this is part of an expression, so why doesn't Rust know to assign the "temporary" value to the variable I'm initializing?
What is the Rust approach to assign one of two alternative values to a constant variable based upon some condition?
Answer
temporary value dropped while borrowed
you need to bind the Command
struct to a name, so it doesn't get dropped while borrowed. then make the if
block evaluate to it.
if cfg!(target_os = "windows") {
let mut cmd = std::process::Command::new("cmd");
cmd.arg("/C");
cmd // value of if block
}
else {
let mut cmd = std::process::Command::new("sh");
cmd.arg("-c");
cmd // value of if block
}
.arg("echo hello")
.output()
.expect("failed to execute process");
in my opinion, platform-dependent functionality should be hidden in a separate method.
#[cfg(target_os = "windows")]
fn command_shell() -> std::process::Command
{
let mut cmd = std::process::Command::new("cmd");
cmd.arg("/C");
cmd
}
#[cfg(not(target_os = "windows"))]
fn command_shell() -> std::process::Command
{
let mut cmd = std::process::Command::new("sh");
cmd.arg("-c");
cmd
}
fn main() {
command_shell().arg("echo hello")
.output()
.expect("failed to execute process");
}