Skip to main content

Module run

Module run 

Source
Expand description

Dispatch: handing a parsed command to the code that carries it out.

A parse ends with a value — an enum whose selected variant holds the command’s own struct — and every CLI then writes the same thing: a match over that enum, one arm per command, each arm calling the one function that command exists to call. At mise’s size that match is 210 arms of pure routing, and the compiler cannot tell that an arm calling the wrong function is wrong, because every arm has the same shape.

So the derive writes it. A command implements Run (or RunWith, when the CLI hands its commands shared state), the enum says #[usage(run)], and the match is generated from the same declaration the parser and the spec come from. Nothing about it reaches the spec: which Rust function carries out a command is not part of what the CLI is, and a spec that recorded it could not be read by anything that is not this program. It is the same rule #[usage(skip)] follows.

Every one of these traits takes self by value. A command is finished when it has run, and the values it parsed are its own — taking them by reference would mean every handler borrowing what nothing else can want.

§Which one

no contexta context
syncRunRunWith
asyncRunAsyncRunAsyncWith

The context is whatever the CLI has to give — a resolved config, an output handle, a database connection — and the With traits are generic over it, so RunWith<&mut App> and RunAsyncWith<Arc<Ctx>> are ordinary implementations rather than shapes this crate has to anticipate.

A context is a separate trait rather than one defaulted to () because the noise otherwise falls on the wrong side of a CLI: a hundred commands that need no context would each carry fn run(self, _: ()), which says nothing and cannot be left out. One type may implement several of these, and one enum may dispatch several, which is what a CLI part-way through adopting a context — or an async runtime — needs.

§Async commands

RunAsync and RunAsyncWith are the async pair: an implementation writes async fn, and the generated dispatch is an async fn that awaits the selected command.

use usage_argv::RunAsync;

struct Install {
    force: bool,
}

impl RunAsync for Install {
    type Output = Result<(), String>;
    async fn run_async(self) -> Self::Output {
        // .await here
        Ok(())
    }
}

The trait declares -> impl Future<Output = Self::Output> rather than async fn, which is the same thing on the implementing side and deliberately imposes no Send bound: a CLI on a single-threaded runtime keeps futures that hold an Rc across an await, and one that spawns gets Send by inference, since it leaks out of the concrete commands the dispatch reaches. What this cannot do is demand Send in generic code, which is the trade the alternative — -> impl Future + Send in the trait — makes in the other direction, and there is no way to have both without duplicating the trait.

The sync pair can carry a future too, since Output is whatever the command produces: a boxed Pin<Box<dyn Future<Output = T>>> (plus + Send if the CLI wants it) is a value like any other. That costs an allocation and names a type; the async traits exist so that neither is necessary.

§An example

use usage_argv::Run;

struct Install {
    force: bool,
}
struct Sponsors;

impl Run for Install {
    type Output = Result<(), String>;
    fn run(self) -> Self::Output {
        if self.force {
            Ok(())
        } else {
            Err("refusing without --force".into())
        }
    }
}

impl Run for Sponsors {
    type Output = Result<(), String>;
    fn run(self) -> Self::Output {
        println!("thanks");
        Ok(())
    }
}

// What `#[usage(run)]` on the subcommand enum generates, written out.
enum Command {
    Install(Install),
    Sponsors(Sponsors),
}

impl Run for Command
where
    Install: Run,
    Sponsors: Run<Output = <Install as Run>::Output>,
{
    type Output = <Install as Run>::Output;
    fn run(self) -> Self::Output {
        match self {
            Command::Install(inner) => Run::run(inner),
            Command::Sponsors(inner) => Run::run(inner),
        }
    }
}

assert!(Command::Install(Install { force: true }).run().is_ok());

Traits§

Run
A command that can be carried out with nothing but what it parsed.
RunAsync
An async command: Run, awaited.
RunAsyncWith
An async command that is handed something shared when it runs: RunWith, awaited.
RunWith
A command that is handed something shared when it runs.