15 releases (stable)
2.1.4 | Jan 24, 2023 |
---|---|
2.1.3 | Jan 23, 2023 |
1.1.5 | Jan 11, 2023 |
0.1.3 | Dec 14, 2022 |
#600 in Rust patterns
15KB
150 lines
dynarg
A simple dynamic argument system
Have you ever wanted to have multiple functions with the same signature, but with very different purposes and behavior?
- Maybe you want to make an image editing app, with lots of tools.
- Maybe you want to make a Rust GCODE implementation.
- Maybe you just want to make a modular shell program and dynamically pick arguments out like fruits off a berry bush, while keeping track of which ones haven't been used.
Regardless, you probably want an API that can:
- Match arguments to static strings, to avoid runtime overhead, while maintaining readability.
- Potentially handle dynamic strings if needed.
- Handle arbitrary argument types.
- Provide convenience functions for working with arguments -- e.g., wrapper functions for common types.
If any of this applies to you, this is a library to consider. Note that for very high-performance applications, it might be better to roll your own custom use case with a Vec (ideally recycled!) of enum, to avoid dynamic dispatch.
This API is at this point considered stable and reasonably mature. It is forbid_unsafe
, and written in pure Rust.
Basic example
use dynarg::Args;
fn main() {
// Creating Args object
let mut args = Args::new();
// Inserting a string type
args.insert_string("greeting", String::from("hello world"));
// Inserting an i32 type
args.insert_i32("meaning_of_life", 42);
// There's a lot more types where that came from, BTW :)
// (In fact, you can use any type that implements `Any`,
// which... I think should be any?)
// Retrieving string type
let out = args.get_string("greeting").unwrap();
println!("{}", out);
// Retrieving i32
let meaning_of_life = args.get_i32("meaning_of_life").unwrap();
println!("The meaning of life is: {}", meaning_of_life);
}
Tracking which argument is used
use dynarg::Args;
fn main() {
// Creating Args object
let mut args = Args::new();
// Inserting a string type
args.insert_string("greeting", String::from("hello world"));
// Inserting an i32 type
args.insert_i32("meaning_of_life", 42);
// There's a lot more types where that came from, BTW :)
// (In fact, you can use any type that implements `Any`, which... I think should be any?)
// Retrieving string type (while marking used flag!)
let out = args.poke_string("greeting").unwrap();
println!("{}", out);
// Retrieving i32 (also while marking used flag!)
let meaning_of_life = args.poke_i32("meaning_of_life").unwrap();
println!("The meaning of life is: {}", meaning_of_life);
// NOTE: the difference between poke and poke_* functions, and get and get_,
// is that poke marks the status as used.
// Note that this only exists if `used` feature is enabled for library.
// Explicitly marking status is used is useful for sanity checking -- e.g.
// Checking used status of args is useful for catching
// what would otherwise be silent or hard-to-catch errors
if args.all_used() {
println!("All used! :3");
} else {
for used_arg_name in args.iter_not_used_name() {
println!("Arg: \"{}\" not used", used_arg_name);
}
}
}
Less basic example
use dynarg::*;
/// Where normally you'd need to have a fixed set of arguments,
/// each of which would be roughly fixed types
/// -- you can dynamically push arguments on the fly instead.
/// This is useful when you need a consistent function signature
/// for different types of functions,
/// each needing different arguments
fn draw(args: &mut Args) {
if let Ok(greeting) = args.poke_string("greeting") {
println!("{} world", greeting);
}
if let Ok(arg) = args.poke::<Fruit>("fruit_to_draw") {
println!("I will draw {}!", arg.0);
if let Ok(size) = args.poke::<f32>("size") {
println!("with a size of {}", size);
}
} else {
panic!("Nothing to draw D:");
}
}
/// A custom struct as an example
struct Fruit<'a>(&'a str);
fn main() {
let mut args = Args::default();
let apple = Fruit("apple");
// This is how you add arguments
args.insert("fruit_to_draw", Box::new(apple));
args.insert("size", Box::new(5.2f32));
let greeting = String::from("henlo");
args.insert_string("greeting", greeting);
draw(&mut args);
if !args.all_used() {
println!("Warning! I didn't use all my arguments D:");
}
// Clear all the used flags on args
args.reset_used_status();
}
PRs welcome :)
Todo
- Custom type handling
- Replace
Option
s withResult
s such that it's possible to identify whether the argument name didn't exist, or the type was wrong - Add
snafu
- Add convenience functions (e.g.
get_string()
,get_int()
) - Properly document gotchas
- Add variant without
used()
functionality. - Add more examples
- Benchmarks
Dependencies
~2.3–4MB
~71K SLoC