| Crates.io | kwarg |
| lib.rs | kwarg |
| version | 0.1.0-alpha.1 |
| created_at | 2026-01-12 21:06:30.675525+00 |
| updated_at | 2026-01-12 21:06:30.675525+00 |
| description | Keyword arguments for Rust functions via proc macros |
| homepage | https://github.com/tcdent/kwarg-rs |
| repository | https://github.com/tcdent/kwarg-rs |
| max_upload_size | |
| id | 2038848 |
| size | 29,688 |
Bringing keyword arguments to Rust, because sometimes you just want to troll.
Rust has beautiful named field syntax for structs:
let foo = Foo {
x: 42,
y: "hello".to_string(),
z: vec![1, 2, 3],
};
But constructors and methods are stuck in the positional dark ages:
let foo = Foo::new(42, "hello".to_string(), vec![1, 2, 3]);
// Wait, which parameter is which again?
This library lets you write:
let foo = kwargs!(Foo::new =>
x: 42,
y: "hello".to_string(),
z: vec![1, 2, 3]
);
Add #[kwarg] to any function or method you want to support keyword arguments:
use kwarg::kwarg;
impl Foo {
#[kwarg]
fn new(x: i32, y: String, z: Vec<i32>) -> Self {
Foo { x, y, z }
}
#[kwarg]
fn configure(&mut self, timeout: u64, retries: u32, verbose: bool) {
// ...
}
}
#[kwarg]
fn greet(name: &str, age: u32, greeting: &str) {
println!("{} {}, you are {}", greeting, name, age);
}
Use the kwargs! macro at the call site:
use kwarg::kwargs;
// For methods
let foo = kwargs!(Foo::new =>
y: "hello".to_string(),
z: vec![1, 2, 3],
x: 42 // Order doesn't matter!
);
// For functions
kwargs!(greet =>
greeting: "Hello",
name: "Alice",
age: 30
);
// Still works for method calls
kwargs!(foo.configure =>
verbose: true,
timeout: 5000,
retries: 3
);
The annotated functions remain unchanged and can be called normally:
// Positional arguments still work
let foo = Foo::new(42, "hello".to_string(), vec![1, 2, 3]);
greet("Alice", 30, "Hello");
// You choose when to use kwargs
Foo::new, self.method(), everythingAdd to your Cargo.toml:
[dependencies]
kwarg = "0.1.0"
#[kwarg]
impl Config {
fn new(
host: String,
port: u16,
timeout: Duration,
max_connections: usize,
enable_tls: bool,
retry_policy: RetryPolicy,
) -> Self {
// ...
}
}
let config = kwargs!(Config::new =>
host: "localhost".to_string(),
port: 8080,
enable_tls: true,
timeout: Duration::from_secs(30),
retry_policy: RetryPolicy::Exponential,
max_connections: 100
);
Instead of this:
let request = HttpRequest::builder()
.method("POST")
.url("https://api.example.com")
.header("Content-Type", "application/json")
.body(body)
.timeout(Duration::from_secs(30))
.build()?;
Write this:
#[kwarg]
fn make_request(
method: &str,
url: &str,
headers: HashMap<String, String>,
body: Vec<u8>,
timeout: Duration,
) -> Result<Response> {
// ...
}
let response = kwargs!(make_request =>
method: "POST",
url: "https://api.example.com",
headers: headers,
body: body,
timeout: Duration::from_secs(30)
)?;
#[kwarg]
fn process<'a>(data: &'a [u8], offset: usize, length: usize) -> &'a [u8] {
&data[offset..offset + length]
}
let result = kwargs!(process =>
data: &buffer,
length: 100,
offset: 50
);
This is partly a proof-of-concept to explore what's possible with Rust's macro system, and partly a genuine ergonomics improvement for functions with many parameters. It's inspired by:
If you find this useful (or hilarious), great! If you think it's an abomination, that's also valid. Rust's macro system is powerful enough to do this, so why not?
The library consists of two proc macros that coordinate via a naming convention:
#[kwarg] attribute macro - Processes function definitionskwargs!() function-like macro - Processes call sitesBoth macros independently compute the same mangled name for a "hidden" declarative macro, allowing them to coordinate without any global registry or shared state.
Why not just one macro?
We need to process both the function definition (to extract parameter names and order) and the call site (to reorder arguments). This requires two separate macros:
#[kwarg] runs at the function definitionkwargs!() runs at the call siteThe core insight: Both macros compute the same mangled name from the function path.
Foo::new → __kwarg_Foo_new
std::fs::File::open → __kwarg_std_fs_File_open
Why this works:
Name mangling algorithm:
1. Take the full path: Foo::new
2. Replace :: with _: Foo_new
3. Prepend __kwarg_: __kwarg_Foo_new
4. This becomes the hidden macro name
Critical requirement: The original function must remain unchanged.
#[kwarg]
fn foo(x: i32, y: String) -> Bar { ... }
// Must still be callable normally:
foo(42, "hello".to_string());
Why: This makes the library non-invasive. You can:
Implementation: The #[kwarg] macro generates a separate hidden macro but leaves the original function untouched.
What #[kwarg] actually does:
// Input:
#[kwarg]
fn greet(name: &str, age: u32, greeting: &str) {
println!("{} {}, you are {}", greeting, name, age);
}
// Output:
fn greet(name: &str, age: u32, greeting: &str) { // Original unchanged!
println!("{} {}, you are {}", greeting, name, age);
}
#[doc(hidden)]
macro_rules! __kwarg_greet {
($($key:ident: $value:expr),* $(,)?) => {{
// Collect arguments by name
let mut __name = None;
let mut __age = None;
let mut __greeting = None;
$(
match stringify!($key) {
"name" => __name = Some($value),
"age" => __age = Some($value),
"greeting" => __greeting = Some($value),
_ => compile_error!(concat!("Unknown parameter: ", stringify!($key))),
}
)*
// Call with correct order
greet(
__name.expect("Missing required parameter: name"),
__age.expect("Missing required parameter: age"),
__greeting.expect("Missing required parameter: greeting"),
)
}};
}
Design choices in the generated macro:
Option<T> for collection: Allows detecting missing/duplicate parametersstringify!($key): Convert parameter names to strings for matchingcompile_error!: Catch unknown parameters at compile time.expect(): Clear runtime error if parameter is missing (shouldn't happen if used correctly)#[doc(hidden)]: Hide from documentation, these are implementation detailsWhat kwargs!() does:
// Input:
kwargs!(Foo::new =>
y: "hello".to_string(),
x: 42
)
// Step 1: Parse the path
// Path: Foo::new
// Mangled: __kwarg_Foo_new
// Step 2: Expand to hidden macro call
__kwarg_Foo_new!(
y: "hello".to_string(),
x: 42
)
// Step 3: That macro reorders and calls
Foo::new(42, "hello".to_string())
Key implementation details:
syn::Path: Handle arbitrary module paths:: paths and . method calls=> to the hidden macroProblem: We don't know the type of obj at macro expansion time!
Solution: Only support static paths:
// Supported
kwargs!(Foo::method => ...)
// Not yet supported, use workaround:
let result = kwargs!(Foo::method => obj: &self, x: 42);