Paw
— 2019-05-06
Today we're announcing paw, a first step by the CLI Working Group to make command line applications more first class in Rust.
We introduce a procedural macro paw::main
that allows passing arguments to fn main
, and a new trait
ParseArgs
that must be implemented by the arguments to main
. This allows passing not only the classic
std::env::Args
to main
but also, for example, structopt
instances.
print_args
fn main(args: std::env::Args) {
for arg in args {
println!("{:?}", arg);
}
}
$ cargo run --example print_args -- hello world
"hello"
"world"
structopt
#[derive(structopt::StructOpt)]
struct Args {
address: String,
port: u16,
}
async fn main(args: Args) -> Result<(), failure::Error> {
let mut app = tide::App::new(());
app.at("/").get(async |_| "Hello, world!");
app.serve((args.address, args.port)).await?;
}
$ cargo run --example structopt -- localhost 8080
Our hope is that by allowing flexible passing of arguments to fn main
we can make command line
parsing feel more intuitive for new Rustaceans and seasoned experts alike.
Paw today
What paw
brings is a stepping stone to enable our vision of first-class command line parsing in
Rust. We envision a place where ParseArgs
is included in stdlib, and std::env::Args
implements
it out of the box. However, as a precursor to an RFC we want to polish the ergonomics and test the
usability of paw
to get it right.
With paw
today the print
example can be written as:
#[paw::main]
fn main(args: paw::Args) {
for arg in args {
println!("{:?}", arg);
}
}
This should provide a comparable experience to what a potential std experience would be like.
How does the trait work?
The trait has 1 method: parse_args
which returns a Result<Self>
. Because this must be known
at compile time, Self
needs to be Sized
, and we have an associated Error
type too. In total
the declaration is about 4 lines, which means as far as traits go it's quite small.
pub trait ParseArgs: Sized {
type Error;
fn parse_args() -> Result<Self, Self::Error>;
}
The paw::main
macro detects if the trait is implemented for the argument passed in at runtime, and then
replaces (args: Args)
with let args = Args::parse_args()
inside the function body. We suspect
that adding similar functionality to std would be a (relatively) small change too.
Conclusion
We've introduced paw
, a crate to enable arguments in main. It consists of a proc macro, trait, and
wrappers around stdlib's std::env::Args
and std::env::ArgsOs
types.
paw
is available on GitHub as rust-cli/paw, and on crates.io as
paw. Happy hacking!
Thanks
Thanks to stjepang and Dylan-DPC for help with the implementation. And all of the CLI WG for help and feedback on the API and this post.