| Crates.io | bobtail |
| lib.rs | bobtail |
| version | 0.2.0 |
| created_at | 2026-01-10 08:54:57.573152+00 |
| updated_at | 2026-01-11 08:35:12.3288+00 |
| description | Generate macro proxies of functions whose tails can be "bobbed" as in cut off |
| homepage | |
| repository | https://github.com/shanecelis/bobtail |
| max_upload_size | |
| id | 2033882 |
| size | 51,116 |
Generate macro proxies of functions whose tails can be "bobbed" as in cut off.
Figure 1. A bobtail machine.
This crate produces macro proxies of functions whose trailing arguments may be omitted or provided with less boilerplate.
The define! macro generates macro proxies for functions and method prototypes.
fn f(a: u8, b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}
bobtail::define! {
fn f(a: u8, #[tail] b: Option<u8>);
}
assert_eq!(f(1, Some(2)), 3); // Call function.
assert_eq!(f!(1), 1); // Call macro with omission.
assert_eq!(f!(1, _), 1); // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3); // Pass unwrapped second argument.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
The bobtail::define! produces macro rules that might have looked like this code.
macro_rules! f {
($a:expr) => {
f($a, None)
};
($a:expr, $b:expr) => {
f($a, Some($b))
};
}
But bobtail::define! can handle the following case, which the above macro can
not.
assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
How? Because instead of being restricted to Option, an ommitable parameter can
be any type that implements Default and From<T>. What bobtail::define!
actually produces is this:
macro_rules! f {
($a:expr) => {
f($a, Default::default())
};
($a:expr, $b:expr) => {
f($a, From::from($b))
};
}
From<T> is not only more flexible, but it permits one to use Some(2) above
because there is a blanket implementation for From<T> for all T, which is an
identity function.
Methods with a &self, &mut self, or self expect the receiver as the first
argument to the macro.
struct A;
impl A {
fn b(&self, a: u8, b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}
fn c(self, a: u8) -> u8 {
a
}
}
bobtail::define! {
fn b(&self, a: u8, #[bobtail::tail] b: Option<u8>) -> u8;
// Name the macro explicitly.
c_macro => fn c(self, #[tail] a: u8); // Return type can be omitted.
}
let a = A;
assert_eq!(a.b(1, Some(2)), 3); // Call function.
assert_eq!(b!(a, 1, Some(2)), 3); // Call macro.
assert_eq!(b!(a, 1, 2), 3); // Omit `Some`.
assert_eq!(b!(a, 1), 1); // Omit second argument.
assert_eq!(b!(a, 1, _), 1); // Explicitly omit second argument.
assert_eq!(c_macro!(a, 4), 4); // Consume self.
let a = A;
assert_eq!(c_macro!(a), 0); // Any `Default` will do.
One can also generate macro proxies with attributes.
#[bobtail::bob]
fn f(a: u8, #[tail] b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}
assert_eq!(f(1, Some(2)), 3); // Call function.
assert_eq!(f!(1), 1); // Call macro with omission.
assert_eq!(f!(1, _), 1); // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3); // Pass unwrapped second argument.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
Methods with a &self, &mut self, or self expect the receiver as the first
argument to the macro proxy.
struct A;
#[bobtail::block]
impl A {
#[bobtail::bob]
fn b(&self, a: u8, #[bobtail::tail] b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}
#[bob(c_macro)] // Name the macro explicitly.
fn c(self, #[tail] a: u8) -> u8 {
a
}
}
let a = A;
assert_eq!(a.b(1, Some(2)), 3); // Call function.
assert_eq!(b!(a, 1, Some(2)), 3); // Call macro.
assert_eq!(b!(a, 1, 2), 3); // Omit `Some`.
assert_eq!(b!(a, 1), 1); // Omit second argument.
assert_eq!(b!(a, 1, _), 1); // Explicitly omit second argument.
assert_eq!(c_macro!(a, 4), 4); // Consume self.
let a = A;
assert_eq!(c_macro!(a), 0); // Any `Default` will do.
This crate was inspired by my work on
Nano-9, a Pico-8 compatibility layer for
Bevy. Pico-8's Lua API has many arguments that are often omitted. Consider
Pico-8's text drawing function print.
-- print(str, [x,] [y,] [color])
print("hello world")
-- No x? No y? No problem.
Nano-9 provides the Lua API as-is, but it also provides a Pico-8-like API in Rust for which the above looks like this:
// print(str, vec2, color, /* Nano-9 extensions: */ font_size, font_index)
pico8.print("hello world", None, None, None, None).unwrap();
The aim of this crate is to offer an API on the Rust side that is not so verbose.
print!(pico8, "hello world").unwrap();
This is my reason for creating this crate, but that does not mean I wholeheartly endorse this kind of positional, omittable, API design. If I were not constrained by Pico-8's initial design and wanting to bear a strong resemblance to it, I would consider using structs expressively as named and omittable arguments potentially using other crates like typed-builder and derive_builder.
Add bobtail to a project with the following command:
cargo add bobtail
This crate is licensed under the MIT License or the Apache License 2.0.
If no #[tail] is present, then all arguments are required.
#[bobtail::bob]
fn f(a: u8, b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}
assert_eq!(f!(1), 1); // Call macro with omission.