Expand description
§bobtail
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.
§Prototypes
The define! macro generates macro proxies for functions and method prototypes.
§Free Functions
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.
#[cfg(feature = "omit-token")]
assert_eq!(f!(1, _), 1); // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3); // Pass unwrapped second argument.§Generated Macro Rules
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
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.
#[cfg(feature = "omit-token")]
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.§Attributes
One can also generate macro proxies with attributes.
§Free Functions
#[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.
#[cfg(feature = "omit-token")]
assert_eq!(f!(1, _), 1); // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3); // Pass unwrapped second argument.§Methods
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.
#[cfg(feature = "omit-token")]
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.§Motivation and Justification
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();§A Caution
The above are my reasons 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 bon, typed-builder, and derive_builder.
§Features
bobtail generates self-contained and straightforward to inspect macros, but one
cannot use the underscore ‘_’ to omit values without using the omit-token
feature.
§omit-token - Enable _ placeholder syntax
If one wants to use _ as a placeholder for default values anywhere in the tail
arguments, enable the omit-token feature:
[dependencies]
bobtail = { version = "0.3", features = ["omit-token"] }With this feature enabled, one can write:
assert_eq!(f!(1, _), 1); // Explicitly use default for second arg
assert_eq!(f!(1, _, 3), ...); // Use default for second, explicit thirdWithout the feature, one would need to pass explicit default values:
assert_eq!(f!(1, None, 0), 1); // Explicitly pass `None`
assert_eq!(f!(1, Default::default(), 0), // Explicitly pass default
1);The trade-off is that omit-token uses a recursive helper macro internally,
which makes macro expansion slightly more complex.
§Advanced Features
§Macro Visibility
By default, the generated macro has the same visibility as the function. One can override this with explicit visibility:
For #[bob] attribute:
#[bobtail::bob(pub(crate) my_macro)] // Define visibility and macro name.
#[bobtail::bob(pub(crate))] // Define macro visibility.
#[bobtail::bob(pub(self))] // Define private macro.For define! macro:
bobtail::define! {
pub(crate) my_macro => fn foo(a, #[tail] b); // Define visibility and macro name.
pub(crate) => fn bar(a, #[tail] b); // Define macro visibility.
pub(self) => fn bar(a, #[tail] b); // Define private macro.
}pubvisibility adds#[macro_export].pub(crate)andpub(in path)use re-exports.pub(self)creates a private macro even for a public function.
§Macro Attributes
Add custom attributes to generated macros using #[bobtail::macro_attrs(...)]:
#[bobtail::macro_attrs(doc(hidden))]
#[bobtail::bob]
pub fn foo(a: u8, #[tail] b: Option<u8>) -> u8 {
b.map(|x| x + a).unwrap_or(a)
}For define!, use outer attributes directly:
bobtail::define! {
#[doc(hidden)]
fn foo(a, #[tail] b);
}§Typeless Parameters in define!
Types are optional in define! since only parameter counts and #[tail]
markers matter for macro generation:
bobtail::define! {
fn foo(a, #[tail] b); // No types needed
}This is equivalent to:
bobtail::define! {
fn foo(a: u8, #[tail] b: Option<u8>) -> u8;
}§Install
Add bobtail to a project with the following command:
cargo add bobtailOr add these lines to the Cargo.toml file:
[dependencies]
bobtail = "0.3"§License
This crate is licensed under the MIT License or the Apache License 2.0.
Macros§
- define
- Specify function or method prototypes that generates a variadic macro proxy allow for omission of elements in its “tail”.