#[laby]
Expand description
Generates a macro that calls a function with named arguments.
Named arguments can be useful when a function accepts several arguments, because explicitly stating the arguments with parameter names can improve readability.
This attribute macro generates a function-like macro, with the same visibility and path as
the target function, which allows callers to call that function with the arguments specified in
any arbitrary order using assignment-like expressions (name = value
).
Although this attribute is provided for use in laby components, its implementation is not specific to laby. It may be applied to any function, albeit with some caveats documented below. Refer to the crate-level documentation for more usage examples.
#[default]
arguments
By default, all arguments must be specified explicitly, even Option<T>
types. Omittable
arguments are opt-in. To mark a parameter as omittable, prepend the #[default]
attribute to
the parameter.
#[laby]
fn foo(x: Option<&str>) {
assert!(x.is_none());
}
#[laby]
fn bar(#[default] x: Option<&str>) {
assert!(x.is_none());
}
foo!(x = None); // required
bar!(x = None); // omittable
bar!(); // omitted; equivalent to the above line
This attribute by default defaults to Default::default()
. This behavior can be customized
by passing a default expression as the attribute argument. The expression is evaluated in the
macro expansion context.
#[laby]
fn test(left: &str, #[default("b")] right: &str) {
assert_eq!(left, right);
}
test!(left = "a", right = "a");
test!(left = "b", right = "b");
test!(left = "b"); // omitted; equivalent to the above line
It is not possible to apply #[default]
on generic parameters like impl Render
because the
compiler cannot infer which default implementation of Render
should be used. This can be
circumvented by using the unit type ()
implementation of Render
as the default
expression, which simply renders nothing.
#[laby]
fn component(#[default(())] title: impl Render) -> impl Render {
article!(
h1!(title),
)
}
assert_eq!(render!(component!()), "<article><h1></h1></article>");
assert_eq!(render!(component!(title = a!("title"))), "<article><h1><a>title</a></h1></article>");
#[rest]
arguments
By default, all arguments must be specified with their respective parameter name. A function
may declare at most one parameter with this attribute, which binds all arguments without a
specified name to that parameter, wrapped together using frag!
. This behavior is similar to
React children.
#[laby]
fn component(#[default(())] title: impl Render, #[rest] children: impl Render) -> impl Render {
article!(
h1!(title),
main!(children),
)
}
assert_eq!(render!(component!()), "<article><h1></h1><main></main></article>");
assert_eq!(render!(component!("para")), "<article><h1></h1><main>para</main></article>");
assert_eq!(render!(component!(p!("para1"), p!("para2"))), "<article><h1></h1><main><p>para1</p><p>para2</p></main></article>");
assert_eq!(render!(component!(title = "laby", p!("para1"), p!("para2"))), "<article><h1>laby</h1><main><p>para1</p><p>para2</p></main></article>");
Caveats
Function must be free-standing
The target function with this attribute must be free-standing; it must be declared at the
module-level, not within a trait
or impl
block. This is because Rust simply does not
support macro declarations in such places.
struct Foo;
#[laby]
fn good(x: &Foo) {}
impl Foo {
// this will not compile:
#[laby]
fn bad(&self) {}
}
Function should not be named after an HTML tag
When a markup macro named after an HTML tag is invoked within another markup macro, laby recognizes this pattern and inlines that nested HTML macro into the parent macro as an optimization, regardless of whether that HTML macro is indeed an HTML macro or another macro with a conflicting name that actually does something completely different. As a workaround, you may alias the function with a different name.
#[laby]
fn article() -> impl Render {
"foo"
}
fn good() {
use article as foo;
let s = render!(div!(foo!()));
assert_eq!(s, "<div>foo</div>");
}
fn bad() {
// refers to `laby::article`, not the `article` macro declared above!
let s = render!(div!(article!()));
assert_eq!(s, "<div><article></article></div>");
}
Macro must be imported into scope
When calling the target function using the generated macro, both that function and the macro
must be imported directly into the current scope. It cannot be called by relative or fully
qualified paths. This is due to hygiene limitations of macro_rules!
which prevent functions
from being referenced within macros unambiguously.
This caveat can be circumvented by enabling the decl_macro
feature.
mod foo {
#[laby]
pub fn bar() {}
}
fn good() {
use foo::bar;
bar!();
}
fn bad() {
foo::bar!(); // no function named `bar` in scope
}
Macro is not exported outside the crate
The generated macro is defined using macro_rules!
which prevents macros from being exported
in modules other than the crate root. Due to this limitation, the maximum visibility of the
generated macro is restricted to pub(crate)
even if the target function is pub
.
This caveat can be circumvented by enabling the decl_macro
feature.
// crate_a
#[laby]
pub fn foo() {}
fn good() {
foo!();
}
// crate_b
fn bad() {
use crate_a::foo; // macro `foo` is private
foo!();
}
Macros 2.0 support
laby comes with support for the experimental Declarative Macros 2.0 compiler feature which
can be enabled using the feature flag decl_macro
. This requires a nightly toolchain.
To enable this feature, add laby’s feature flag in your Cargo.toml
,
[dependencies]
laby = { version = "...", features = ["decl_macro"] }
and enable the compiler’s feature flag in your crate root.
#![feature(decl_macro)]
The generated macros will now use the new macro foo { ... }
syntax instead of macro_rules! foo { ... }
.