Pass Command directly into function

I have a function which takes a std::process::Command object and then goes ahead and calls it, doing stuff with the output. I want the function to be able to take custom commands, along with their arguments (and maybe other things like environment variables).

I expect the function to take ownership of the Command passed in, so I defined it like:

fn do_thing(mut c: Command) {
    println!("{:?}", c.output().unwrap());
}

The problem

I can call it with

do_thing(Command::new("ls"));

which works fine. However, I want to be able to e.g. pass in arguments, like:

do_thing(Command::new("ls").arg("."));

but that causes the compiler error:

do_thing(Command::new("ls").arg("."));
-------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Command`, found `&mut Command`
|
arguments to this function are incorrect

This happens because the new() part of the builder returns a Command object, but subsequent steps in the builder (like arg()) return &mut Command. I'd hoped that the compiler would be able to see that this object is about to go out of scope and so allow it to be cast and moved directly.

A workaround

I can work around the issue, by making a mut object first, mutating it, and then passing in the object

let mut c = Command::new("ls");
c.arg(".");
do_thing(c);

but that takes what "should" be one line and makes it three.

A partial solution

In my specific case, I can change my function to take just the command name and argument list as parameters like:

fn do_thing_differently(name: &str, args: &[&str]) {
    println!("{:?}", Command::new(name).args(args).output().unwrap());
}

which I can call with

fn another_workaround() {
    do_thing_differently("ls", &["."]);
}

but I'd like to understand how one is "supposed" to solve this problem.

Is there some "deref and move" thing I can do, or a simple change to the function parameters for do_thing?

I have an example at Rust Playground.

Could you just take the command by mutable reference in the first place? Most useful methods such as spawn() take &mut self, so transferring ownership is not needed.

1 Like

Changing your signature to do_thing(c: &mut Command) and calling it with do_thing(&mut Command::new("ls")) would probably be the easiest fix?

use std::process::Command;

fn do_thing(c: &mut Command) {
    println!("{:?}", c.output().unwrap());
}

fn main() {
    do_thing(Command::new("ls").arg("."));
    do_thing(Command::new("ls"));
}

Playground.

What it boils down to, is to find some common denominator for both Command and &mut Command that somehow allows you to access to the Command::output method. That could also be a trait, BorrowMut, for example:

use std::process::Command;
use std::borrow::BorrowMut;

fn do_thing(mut c: impl BorrowMut<Command>) {
    println!("{:?}", c.borrow_mut().output().unwrap());
}

fn main() {
    do_thing(Command::new("ls").arg("."));
    do_thing(Command::new("ls"));
}

Playground.

Or you could implement your own trait for both Command and &mut Command:

use std::io::Result;
use std::process::{Command, Output};

trait MyCommandExt {
    fn output(&mut self) -> Result<Output>;
}

impl MyCommandExt for Command {
    fn output(&mut self) -> Result<Output> {
        self.output()
    }
}

impl MyCommandExt for &mut Command {
    fn output(&mut self) -> Result<Output> {
        (*self).output()
    }
}

fn do_thing(mut c: impl MyCommandExt) {
    println!("{:?}", c.output().unwrap());
}

fn main() {
    do_thing(Command::new("ls").arg("."));
    do_thing(Command::new("ls"));
}

Playground.

2 Likes

You could implement a custom trait that replaces the mutable reference with a dummy value à la mem::take(): Playground

trait CommandExt {
    fn take(&mut self) -> Command;
}

impl CommandExt for Command {
    fn take(&mut self) -> Command {
        mem::replace(self, Command::new("dummy"))
    }
}
2 Likes

Thank you both. That's really useful. I've learned something :slight_smile: I realise now that in my real case, things are complicated by the fact that the do_thing function is actually async and run via tokio::spawn so the lifetimes become more difficult, but it's certainly got me closer to a solution.

1 Like

In that case, you will likely benefit from sticking to the boring and straightforward "three line" solution:

let mut c = Command::new("ls");
c.arg(".");
tokio::spawn(do_thing(c));

Spawning async tasks requires they be 'static, so you really don't want an async fn that's going to get spawned to take any &mut, unless you can embed the needed borrows into an async move { } block. (Though, also, a non-async function can explicitly return a Future which doesn't capture its arguments.)

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.