Module derive_adhoc::doc_introduction
source · Expand description
§Introduction to derive-adhoc
derive-adhoc
allows you to write #[derive]
macros
– macros driven by Rust data structures -
by writing templates in a fairly straightforward template language.
This is the friendly introduction. Of course there is a reference manual too.
- Getting started with
derive_adhoc
. - A brief tutorial: How to write your own Clone
- Some more advanced topics
- Another example: Defining a constructor function.
- Other features
§Getting started with derive_adhoc
.
There are two parts to using derive_adhoc
:
specifying templates that you can use to derive new features for your
structs and enums
and then applying those templates to your types.
To define a template, you use
define_derive_adhoc!
, as in
use derive_adhoc::define_derive_adhoc;
define_derive_adhoc! {
NamePrinter =
impl $ttype {
pub fn print_name() {
println!("The name of this type is {}", stringify!($ttype));
}
}
}
This is a very simple template: it uses a single expansion:
$ttype
.
(We’ll get into more expansions, and more useful examples, later on.
For now, all you need to know
is that $ttype
expands to the type
on which you’re applying your template.)
Later on, you can apply NamePrinter
to your own type,
with
#[derive(Adhoc)]
,
as in:
use derive_adhoc::Adhoc;
#[derive(Clone, Debug, Adhoc)]
#[derive_adhoc(NamePrinter)]
pub struct MyStruct;
MyStruct::print_name();
§Exporting templates
But now suppose that you want to expose your template, so that people can use it from any crate.
To do this, you use
pub
before the name of your macro.
pub trait NamePrinter {
fn print_name();
}
derive_adhoc::define_derive_adhoc! {
/// Derives `NamePrinter`, providing `print_name`
pub NamePrinter =
impl $crate::NamePrinter for $ttype {
fn print_name() {
println!("The name of this type is {}", stringify!($ttype));
}
}
}
Note that this time,
we’ve defined NamePrinter
as a trait,
and we’ve changed our template to refer to that trait as
$crate::NamePrinter
.
The
$crate
syntax will expand to the name of the crate
in which our template was defined,
so that when later we expand this template,
it can find the right template.
We’ve also added a doc comment, which will appear in the public API documentation for our crate.
Additionally,
we need to re-export derive_adhoc
from our crate, so that users get the correct version:
// you might want to apply #[doc(hidden)] to this.
pub use derive_adhoc;
Now, when somebody wants to use our template, they can do it like this:
// Let's pretend our crate is called name_printer.
use name_printer::{
// This is the trait we defined...
NamePrinter,
// This is the macro that makes our template work.
// (We might come up with a better syntax for this later).
derive_adhoc_template_NamePrinter.
};
use derive_adhoc::Adhoc;
#[derive(Adhoc)]
#[derive_adhoc(NamePrinter)]
struct TheirStructure {
// ...
}
§If you’re only deriving once…
If you want, you can apply a template to an existing type without having to name that template. You might want to do this if you have a template that you only want to apply to a single struct, and so you don’t want to bother naming it.
Supposing that you wanted to apply the template above to MyStruct
and MyStruct
alone,
you could have said:
use derive_adhoc::{Adhoc, derive_adhoc};
#[derive(Clone, Debug, Adhoc)]
pub struct MyStruct;
derive_adhoc!{
MyStruct:
impl $ttype {
pub fn print_name() {
println!("The name of this type is {}", stringify!($ttype));
}
}
}
Of course, that’s not so useful yet.
In this case, it would have been easier just to write
impl MyStruct { pub fn print_name() { ... } }
.
But soon, we’ll see how to write more interesting templates,
and how to use them to create much more interesting code.
The rest of this document will focus on how to use derive_adhoc’s template features to your fullest advantage. If you’re the kind of person who wants to skip straight to the reference manual, you can find it over here.
§A brief tutorial: How to write your own Clone
For the next few sections, for a toy example,
we’ll be using derive_adhoc
to define
our own version of derive(Clone)
.
At first, it will be very simple;
later on, we’ll add a few features
that Rust’s derive(Clone)
doesn’t have.
Aside:
We’ve picked a simple trait to derive on purpose, so that we can focus on the features of
derive_adhoc
without the additional complexity of introducing an unfamiliar trait as well.Please let us know if this approach works for you! We’re learning how to explain these concepts as we go along.
§Simple templates: fields and repetition
Let’s imagine we had to write Clone from scratch for a simple structure like this:
struct GiftBasket {
n_apples: u8,
n_oranges: u8,
given_from: Option<String>,
given_to: String,
}
We’d probably write something like this:
impl Clone for GiftBasket {
fn clone(&self) -> Self {
Self {
n_apples: self.n_apples.clone(),
n_oranges: self.n_oranges.clone(),
given_from: self.given_from.clone(),
given_to: self.given_to.clone()
}
}
}
(In reality,
since n_apples
and n_oranges
both implement Copy
,
you wouldn’t actually call clone()
on them.
But since the compiler knows their types,
it should be smart enough to
optimize the Clone away for you.)
If you imagine generalizing this to any simple struct struct with named fields, you might come up with a pseudocode template like this one:
impl Clone for ⟪Your struct⟫ {
fn clone(&self) -> Self {
Self {
for each field:
⟪field name⟫: self.⟪field name⟫.clone(),
}
}
}
And here’s how that pseudocode translates into
a derive_adhoc
template:
use derive_adhoc::define_derive_adhoc;
define_derive_adhoc! {
MyClone =
impl Clone for $ttype {
fn clone(&self) -> Self {
Self {
$( $fname : self.$fname.clone() , )
}
}
}
}
Let’s look at that template. You’ve already seen $ttype
: it expands
to the type on which you are applying the macro. There are two new
pieces of syntax here, though:
$( ... )
and
$fname
.
In derive_adhoc
templates, $( ... )
denotes repetition:
it repeats what is inside it
an “appropriate” number of times.
(We’ll give a definition of “appropriate” later on.)
Since we want to clone every field in our struct,
we are repating the field: self.field.clone() ,
part of our implementation.
The $fname
expansion means “the name of a field”.
Which field?
Since $fname
occurs inside $( ... )
,
we will repeat the body of the $( ... )
once for each
field, and expand $fname
to the name of a different field each time.
(Again, more advanced repetition is possible; there’s more to come.)
§On naming
Many
derive_adhoc
expansions’ names start witht
for top-level (whatever you are applying the template to),v
for variant (a variant of anenum
), orf
for field (a single field of a struct or variant).So far, you’ve seen
$ttype
for “top-level type” and$fname
for “field name”.(We say “top-level” instead of “struct”: later on, we’ll be showing you how to apply derive_adhoc to
enums
.)Many
derive_adhoc
expansions end with a short identifier for what they contain. For example,$tname
is the name of a top-level type,$vname
is the name of a variant, and$fname
is the name of a field. Whenever possible, we have tried to use the same identifier for thet
,v
, andf
cases, whenever it is logical.
§Will MyClone apply to other kinds of struct?
Rust defines several kinds of struct:
structs with fields (struct Foo {...};
),
tuple structs (struct Foo(...);
),
and unit structs (struct Foo;
).
If you try to apply the MyClone
template above
to a struct with fields,
it will work just fine.
But with a tuple struct, or a unit struct,
you might expect it to fail.
Surprisingly, it will still work fine!
This isn’t because of any clever trickery
from derive_adhoc
:
it’s just how Rust works.
When you use it on tuple or unit structs,
the MyClone
template we wrote above will expand
to code like this…
which happens to be valid syntax!
struct TupleStruct(String, Option<String>);
impl Clone for TupleStruct {
fn clone(&self) -> Self {
Self {
0: self.0.clone(),
1: self.1.clone(),
}
}
}
struct UnitStruct;
impl Clone for UnitStruct {
fn clone(&self) -> Self {
Self {
}
}
}
This will be a common theme in what follows: Rust often lets you use a slightly unidiomatic syntax so that you can handle many different cases in the same way.
§Making MyClone apply to generics
But here’s a structure where our current MyClone
implementation
will fall flat:
struct MyItems<T:Clone, U>
where U: Clone + Debug
{
things: Vec<T>,
items: Vec<U>
}
When we go to expand the template, it will generate something like:
impl Clone for MyItems { ... }
That isn’t valid! We need to use the generic parameters, like so:
impl<T:Clone, U> Clone for MyItems<T,U>
where U: Clone+Debug
{ ... }
We can expand our MyClone
definition to look that way:
define_derive_adhoc! {
MyClone =
impl<$tgens> Clone for $ttype
where $twheres
{
fn clone(&self) -> Self {
Self {
$( $fname : self.$fname.clone() , )
}
}
}
}
Here we meet two new expansions.
$tgens
(“top-level generics”) becomes
the generic parameters as declared on the top-level type.
(In our case, that’s $T:Clone, U
.)
$twheres
(“top-level where clauses”) becomes
the where
constraints as declared on the top-level type.
(In our case, that’s U: Clone+Debug
.)
Note that $ttype
expands to the top-level type:
that’s now MyItems<T,U>
,
which is what we want.
If we had wanted only MyItems
,
we would say $tname
instead.
Will this template still work for non-parameterized types? Again, yes! To Rust, this syntax is perfectly fine:
struct Simple {
a: String
}
impl<> Clone for Simple
where
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
}
}
}
§Making MyClone apply conditionally
Now, for the first time, we will make MyClone do something
that Rust’s #[derive(Clone)]
does not:
it will apply only when the fields of a struct are Clone
.
For example, suppose have a struct like this:
struct Indirect<T>(Arc<T>, u16);
If you try to derive Clone
on it,
the compiler will generate code something like this:
impl<T: Clone> Clone for Indirect<T> { ... }
But that T: Clone
constraint isn’t necessary: Arc<T>
always
implements Clone
, so your struct could be clone unconditionally.
But using derive_adhoc
,
you can define a template
that derives Clone
only for the cases
where the actual required constraints are met:
define_derive_adhoc! {
MyClone =
impl<$tgens> Clone for $ttype
where $twheres
$( $ftype : Clone , )
{
fn clone(&self) -> Self {
Self {
$( $fname : self.$fname.clone() , )
}
}
}
}
Here, we are using
$ftype
,
the type of a field.
Since we’re repeating it with $( ... )
,
we are requiring every field to be Clone
.
Will this work with non-generic fields, or if the same field is used more than once? Once again, yes! To Rust, this is a perfectly valid example:
impl<T> Clone for Direct
where
T: Clone,
T: Clone,
String: Clone
{
...
}
This time,
derive_adhoc
has exactly one piece cleverness at work. It makes sure that either$twheres
is empty, or that it ends with a comma. That way, your template won’t expand to something likewhere U: Debug + Clone T: Clone
(which is a syntax eror) or soemthing likewhere ,
(which is also a syntax error).
§Deriving for enumerations
At this point, you’ve probably noticed
that we’ve defined MyClone
to apply to struct
s only,
but it won’t (yet) work on enum
s.
Let’s fix that!
Suppose that we have enumeration defined like this:
enum AllTypes {
NoData,
Tuple(u8, u16),
Struct { a: String, b: String }
}
We want to make sure that MyClone can recognize and re-construct each of the three variants.
We can do that as follow (For simplicity, we’re going to ignore generics for now.)
define_derive_adhoc! {
MyClone =
impl Clone for $ttype
{
fn clone(&self) -> Self {
match self {
$(
$vpat => $vtype {
$(
$fname: $fpatname.clone(),
)
},
)
}
}
}
}
Note that now we have two levels of nested repetition.
First, we match once for each variant.
(This is at the $vpat
and $vtype
level.)
Then we match once for each field of each variant.
(This is at the $fname
and $fpatname
level.)
Let’s go over the new expansions here.
First, we have
$vpat
:
that expands to a pattern that can match and deconstruct
a single variant.
Then, we have
$vtype
:
that’s the type of the variant,
suitable for use as a constructor.
Then, inside the variant, we have $fname
:
that’s our field name, which we’ve seen it before.
Finally, we have
$fpatname
:
that is the name of the variable that we used for this field
in the pattern that deconstructed it.
When we apply MyClone
to our enumeration,
we get something like this:
impl Clone for AllTypes {
fn clone(&self) -> Self {
match self {
AllTypes::NoData {} => AllTypes::NoData {},
AllTypes::Tuple {
0: f_0,
1: f_1,
} => AllTypes::Tuple {
0: f_0.clone(),
1: f_1.clone()
},
AllTypes::Struct {
a: f_a,
b: f_b,
} => AllTypes::Struct {
a: f_a.clone(),
b: f_b.clone()
},
}
}
}
Note that our template above will still work fine on a regular struct,
even though it’s written for an enum
.
If we apply MyClone
above
to struct Example { a: u8, b: String }
,
we get this:
impl Clone for Example {
fn clone(&self) -> Self {
match self {
Example {
a: f_a,
b: f_b,
} => Example {
a: f_a.clone(),
b: f_b.clone(),
}
}
}
}
So (in this case at least)
we were able to write a single template expansion
that worked for both struct
s and enum`s.
§Putting the generics back into our enumeration-friendly template
Now let’s see how it works when we try to handle generics again. (It’s surprisingly straightforward!)
define_derive_adhoc! {
MyClone =
impl<$tgens> Clone for $ttype
where $( $ftype: Clone, )
$twheres
{
fn clone(&self) -> Self {
match self {
$(
$vpat => $vtype {
$(
$fname: $fpatname.clone(),
)
},
)
}
}
}
}
§A further note on reptition
Note that when we define our additional where
clauses,
we don’t have to specify separate of repetition
for variants and fields:
if we just have $ftype
in a top-level repetition,
derive_adhoc
will iterate over all fields in all variants.
Sometimes, if you do something subtle,
derive-adhoc may not be able to figure
out what you’re trying to repeat over.
You can use
${for ...}
to specifiy explicitly:
so, above, instead, we could have written
${for fields { $ftype: Clone, }}
You can also use ${for ...}
rather than $(...)
in cases where you feel
it makes your macro code clearer.
§Some more advanced topics
Now that we’ve our first basic example under our belt,
let’s look at some other things that derive_adhoc
can do.
§Transforming names and strings
Often, it’s useful to define new identifiers based on existing ones, or to convert identifiers into strings.
You could use the existing paste
crate for this,
or you can use a native facility provided by derive_adhoc
.
For example, suppose that you want
to define a template that makes a “discriminant” type
for your enumerations.
You want the new type to be named FooDiscriminant
,
where Foo
is the name of your existing type.
While you’re at it, you want to add an is_
function
to detect each variant.
You can do that like this:
define_derive_adhoc! {
Discriminant =
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
enum ${paste $tname Discriminant} {
$(
$vname,
)
}
impl<$tgens> $ttype where $twheres {
fn discriminant(&self) -> ${paste $tname Discriminant} {
match self {
$(
$vpat => ${paste $tname Discriminant}::$vname,
)
}
}
$(
fn ${paste is_ ${snake_case $vname}} (&self) -> bool {
self.discriminant() ==
${paste $tname Discriminant} ::$vname
}
)
}
}
Here we see a couple of new constructs.
First, we’re using
${paste}
to glue several identifiers together into one.
When we say ${paste $tname Discriminant}
,
we are generating a new identifier from $tname
(the type name)
and the word Discriminant.
So if the type name is Foo
,
the new type will be called FooDiscriminant
.
Second, we’re using
${snake_case}
to transform an identifier into snake_case
(that is, lowercase words separated by underscores).
We use this to turn the name of each variant ($vname
)
into a name suitable for use in a function name.
So if a variant is called ExampleVariant
,
${snake_case $vname}
will be example_variant
,
and ${paste is_ ${snake_case $vname}}
will be
is_example_variant
.
There are other case-changers:
${pascal_case my_ident}
becomesMyIdent
. You can also write this as${upper_camel_case ..}
.${lower_camel_case my_ident}
becomesmyIdent
.${shouty_snake_case MyIdent}
becomesMY_IDENT
.${snake_case MyIdent}
becomesmy_ident
, as you’ve already seen.
You can abbreviate ${paste ...}
as $<...>
.
§A note on syntax
In this last section,
you’ve seen a new syntax for the first time.
Both ${paste ident ident..}
and ${snake_case ident}
are special cases of the following meta-syntax,
which derive_adhoc
uses everywhere:
In fact, if you want,
you can use this format
for all of the expansion macros you have already seen:
$ttype
is just a shortened form for ${ttype}
,
$fname
is just ${fname}
,
and so on.
Some keywords, including some of those we’ve already seen, can take named arguments. The syntax for this is:
${KEYWORD ARGNAME=VALUE ARGNAME=VALUE...}
For example, we can use this syntax to give optional arguments to
$vpat
; see the template syntax reference for more information.
If you ever need to write a literal $
(say, if you want to confuse yourself
by making derive-adhoc-generated pattern macros)
you can write $$
.
§Another example: Defining a constructor function.
In this section,
we’ll be using another example to demonstrate
more of what derive_adhoc
can do.
We’ll be building a Constructor
template
to define a new()
function for a struct,
without having to write out all of its arguments.
Let’s start with the following (struct-only) template:
define_derive_adhoc! {
Constructor =
impl<$tgens> $ttype where $twheres {
pub fn new( $( $fname: $ftype , ) ) -> Self {
Self {
$( $fname , )
}
}
}
}
When you apply the above template to a type like this:
use derive_adhoc::Adhoc;
#[derive(Adhoc)]
#[derive_adhoc(Constructor)]
struct Ex<A> {
a: f64,
b: A,
c: String
}
You’ll get a constructor like this:
impl<A> Ex<A> {
pub fn new( a: f64, b: A, c: String ) -> Self {
Self { a, b, c }
}
}
So far, there aren’t any new techniques at work here. We’ll add some more down below.
§Marking a template’s limitations
The template above doesn’t work for enumerations. If you try to apply it to one, you’ll get a not-entirely-helpful error message.
In earlier examples, we’ve shown how to make templates that apply to enums as well as structs. But let’s say that in this case, we want our template to be struct-only.
We can tell derive_adhoc
about this restriction,
to help it generate more useful error messages:
define_derive_adhoc! {
Constructor for struct = // (1)
impl<$tgens> $ttype where $twheres {
pub fn new( $( $fname: $ftype , ) ) -> Self {
Self {
$( $fname , )
}
}
}
}
(Note the use of
for struct
above at // (1)
.)
Now if we try to apply our template to an enum, we’ll get a more useful error:
error: template defined for struct, but applied to enum
§Debugging templates
When writing complex templates, it can sometimes be hard to figure out compiler errors. derive-adhoc has some features which can help:
§Syntax checking the expansion
You can tell derive-adhoc what your macro is supposed to produce:
define_derive_adhoc! {
Constructor for struct, expect items =
impl<$tgens> $ttype where $twheres {
// ...
}
}
If the expansion fails to syntax check, you’ll get not only an error pointing at the part of the template or structure which seems wrong, but also error messages pointing into a copy of the actual expansion. Hopefully you’ll be able to see what’s wrong, there.
If your macro is supposed to expand to an expression,
you can write expect expr
instead of expect items
.
Alternatively, you can request this syntax check when you invoke the macro:
#[derive(Adhoc)]
#[derive_adhoc(Constructor[expect items])]
struct MyStruct { /* ... */ }
§Seeing a copy of the expansion
Sometimes, the expansion syntax checks, but is wrong in some other way.
You can ask derive-adhoc to simply print a copy of the expansion.
#[derive(Adhoc)]
#[derive_adhoc(Constructor[dbg])]
struct MyStruct { /* ... */ }
You’ll see the compiler print something like this:
---------- derive-adhoc expansion of Constructor for MyStruct (start) ----------
impl<> MyStruct where {
pub fn new(...) { ... }
}
---------- derive-adhoc expansion of Constructor for MyStruct (end) ----------
Like the dbg!
macro, you don’t want to
leave this in your production code.
§Working with visibility
Our Constructor
template above doesn’t really make sense
if it’s applied to a non-public type:
Rust may even complain that we’re declaring
a public function that ruturns a private type!
Let’s fix this, and have it give our constructor the same visibility as the type itself:
define_derive_adhoc! {
Constructor for struct =
impl<$tgens> $ttype where $twheres {
$tvis fn new( $( $fname: $ftype , ) ) -> Self {
Self {
$( $fname , )
}
}
}
}
Here instead of saying pub fn new
,
we said $tvis fn new
.
The
$tvis
keyword will expand
to the visibility of the top-level type.
There is a similar similar
$fvis
that expands to the visibility of the current field.
(Since enums variants are always visible, there is no $vvis
.)
§Using attributes to make a template take arguments
Let’s suppose we want to make our Constructor
template
a little more flexible:
we’d like to be able to give the new
function a different name.
We could do this as follows:
define_derive_adhoc! {
Constructor for struct =
impl<$tgens> $ttype where $twheres {
pub fn ${tmeta(newfn)} // (1)
( $( $fname: $ftype , ) ) -> Self {
Self {
$( $fname , )
}
}
}
}
use derive_adhoc::Adhoc;
#[derive(Adhoc)]
#[derive_adhoc(Constructor)]
#[adhoc(newfn="construct_example")]
struct Example {
a: f64,
b: String
}
Here, instead of specifying “new”
for the method name in our template,
we give the name as ${tmeta(newfn)}
.
This tells the template to look for an
#[adhoc(newfn="...")]
attribute on the type,
and to use the value of that attribute
in place of the keyword.
If we want our attribute to be more namespaced,
we can instead say something like
${tmeta(Constructor(newfn = "..."))}
.
If we do, the template will look for an attribute like
#[adhoc(Constructor(newfn = "..."))]
.
The
$tmeta
keyword that we used here
tells the template
to look at the #[adhoc]
attributes for the type.
We can, instead, use
$vmeta
to look for #[adhoc]
attributes for the current variant,
or
$fmeta
to
to look for #[adhoc]
attributes for the current field.
§Getting started with conditionals
In the example above,
we made it possible to rename the “new” function
generated by our template.
But our approach is flawed:
if the user doesn’t provide
the #[adhoc(newfn)]
adhoc attribute,
the template won’t make a function name at all!
Let’s show how to fix that:
define_derive_adhoc! {
Constructor for struct =
impl<$tgens> $ttype where $twheres {
pub fn
${if tmeta(newfn) { ${tmeta(newfn)} } else { new } } // (1)
( $( $fname: $ftype , ) ) -> Self {
Self {
$( $fname , )
}
}
}
}
Have a look at the line marked with // (1)
.
It introduces a new concept: conditional expansion.
The
${if ...}
keyword checks whether a given condition is true.
If it is, then it expands to one of its arguments.
Otherwise, it expands to an “else” argument (if any).
Also, you can chain
$if
s, as in${if COND1 { ... } else if COND2 { ... } else { ... }
and so on!
Here, the condition is
tmeta(newfn)
.
That condition is true if the current type
has an #[adhoc(newfn)]
attribute,
and false otherwise.
There are also vmeta
and fmeta
attributes
to detect #[adhoc(..)]
attributes
on variants and fields respectively.
§More complicated conditionals
Frequently, we’d like our template to behave in different ways different fields. For example, let’s suppose that we want our template to be able to set fields to their default values, and not take them as arguments.
We could do this with an explicit conditional for each field:
define_derive_adhoc! {
Constructor =
impl<$tgens> $ttype where $twheres {
pub fn new
( $(
${when not(fmeta(Constructor(default))) } // (1)
$fname: $ftype ,
) ) -> Self {
Self {
$( $fname:
${if fmeta(Constructor(default)) { Default::default() }
else { $fname } }
, )
}
}
}
}
use derive_adhoc::Adhoc;
#[derive(Adhoc)]
#[derive_adhoc(Constructor)]
struct Foo {
#[adhoc(Constructor(default))]
s: Vec<String>,
n: u32,
}
Here we’re using a new construct:
$when
.
It’s only valid inside a loop like $( ... )
.
It causes the output of the loop to be surpressed
whenever the condition is not true.
The condition in this cases is not(fmeta(Constructor(default)))
.
You’ve seen fmeta
before;
not
is just how we express negation.
All together, this $when
keyword causes each field
that has #[adhoc(Constructor(default))]
applied to it
to be omitted from the list of arguments
to the new()
function.
You can use other boolean operators in conditions too:
there is an
any(...)
that is true
whenever at least one of its arguments is true,
and an
all(...)
that is true
when all of its arguments are true.
§Other features
derive-adhoc has many more features, that aren’t yet explained in this tutorial. For example:
-
is_enum
,is_struct
,is_union
;v_is_unit
,is_named
,is_tuple
;fvis
,tvis
: andapprox_equal
: more conditions for dealing with various cases by hand. -
$tdefkwd
;$tdeftype
;$fdefvis
;$fdefgens
; and$tdefvariants
,$vdefbody
,$fdefine
for defining a new data structure in terms of features of the input data structure, and$Xattrs
for passing through attributes. -
${select1}
can help with writing careful templates that will reject incoherent inputs. -
#{define }
and${defcond }
to make user-defined reuseable template keywords, to save repetition in templates.
Full details are in the reference, which also has a brief example demonstrating each construct.