# bobtail
Generate macro proxies of functions whose tails can be "bobbed" as in cut off.
<p align="center">
<img height="300" alt="A bobtail machine" src="https://github.com/user-attachments/assets/68f58156-1308-4bb8-9700-546733d6d18b"><br>
<em>Figure 1. A bobtail machine.</em>
</p>
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
```rust
fn f(a: u8, b: Option<u8>) -> u8 {
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.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
```
### Generated Macro Rules
The `bobtail::define!` produces macro rules that might have looked like this code.
```rust,ignore
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.
``` rust,ignore
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:
```rust,ignore
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.
```rust
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
```rust
#[bobtail::bob]
fn f(a: u8, #[tail] b: Option<u8>) -> 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.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
```
### Methods
Methods with a `&self`, `&mut self`, or `self` expect the receiver as the first
argument to the macro proxy.
```rust
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](https://github.com/shanecelis/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`.
``` lua
-- 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:
``` rust,ignore
// 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.
``` rust,ignore
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](https://crates.io/crates/bon),
[typed-builder](https://crates.io/crates/typed-builder), and
[derive_builder](https://crates.io/crates/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:
```toml
[dependencies]
bobtail = { version = "0.3", features = ["omit-token"] }
```
With this feature enabled, one can write:
```rust,ignore
assert_eq!(f!(1, _), 1); // Explicitly use default for second arg
assert_eq!(f!(1, _, 3), ...); // Use default for second, explicit third
```
Without the feature, one would need to pass explicit default values:
```rust,ignore
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:
```rust,ignore
#[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:
```rust,ignore
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.
}
```
- `pub` visibility adds `#[macro_export]`.
- `pub(crate)` and `pub(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(...)]`:
```rust,ignore
#[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:
```rust,ignore
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:
```rust,ignore
bobtail::define! {
fn foo(a, #[tail] b); // No types needed
}
```
This is equivalent to:
```rust,ignore
bobtail::define! {
fn foo(a: u8, #[tail] b: Option<u8>) -> u8;
}
```
## Install
Add bobtail to a project with the following command:
``` sh
cargo add bobtail
```
Or add these lines to the Cargo.toml file:
```toml
[dependencies]
bobtail = "0.3"
```
## License
This crate is licensed under the MIT License or the Apache License 2.0.