1 unstable release
0.1.0 | Jun 21, 2022 |
---|
#2568 in Rust patterns
8KB
60 lines
Template.rs
A rust library for making idiomatic, declarative, builder-like patterns that use the struct literal syntax.
No function-like macros required!
How to use
To make a template, just as you would make a builder, you have to consider the following:
- All the parameters
- The default state of the template
- What the template builds into.
- Its relationship with other objects.
You can define a template using the #[template]
annotation
to your template model struct, and the Template
trait.
1. Defining the parameters
Simply use the the #[template]
annotation on a struct with
all the parameters as its fields
#[template]
pub struct Box {
orientation: Orientation,
spacing: i32,
padding: i32,
margin: i32,
}
#[template]
pub struct Button {
padding: i32,
margin: i32,
style: StyleDescriptor,
text: String
}
2. The default state of the template
By default, this library defines the default state of your
template by #[derive(Default)]
. Custom Default
implementations
are also planned.
3. What the template builds into
Use the Template
trait to define what the template builds into, and how
impl Template for Box {
type Output = some_other_lib::Box;
fn define(self) -> Self::Output {
let mut this = some_other_lib::Box::new();
this.padding = self.padding;
//...
this
}
}
impl Template for Button {
type Output = some_other_lib::Button;
fn define(self) -> Self::Output {
let mut this = some_other_lib::Button::new();
this.padding = self.padding;
//...
this
}
}
4. Its relationship with other objects.
Define different default states by dependency injection by objects relative to the template's target.
trait Container {
fn child<T, W>(&self) -> T
where T: Template<Output = W>,
W: some_other_lib::Widget;
}
impl Container for some_other_lib::Box {
fn child<T, W>(&self) -> T
where T: Template<Output = W>,
W: some_other_lib::Widget
{
let this = self.clone();
let out = Button::default(); // The template
out.on_create(move |w| this.add(w)); // provided by the #[template] annotation
out
}
}
Using the template
All annotated templates implement the following traits for building the target types:
for<A, F: FnOnce(Self::Output) -> A> FnOnce(F) -> A
FnOnce() -> Self::Output
Those traits can be invoked right after the struct literal in a "currying" fashion.
This is a simple example of idiomatic templates:
Box {
orientation: Orientation::HORIZONTAL,
padding: 6,
spacing: 6,
..Default::default()
} (|w| {
Box {
orientation: Orientation::VERTICAL,
spacing: 6,
..w.child()
} (|w| {
Button {
text: "Column btn 1",
..w.child()
}();
// Function call constructs button
// and adds it to the box as per the
// ..w.child() injection directive
Button {
text: "Column btn 2"
..w.child()
}();
}); // function call runs the lambda argument
// on the output and then returns it
Button {
text: "Big btn",
..w.child()
}();
})
// expected result:
// |--------------|---------|
// | Column btn 1 | |
// |--------------| Big Btn |
// | Column btn 2 | |
// |--------------|---------|
In case you don't want to use direct function calls, you can use the following equivalent methods:
fn build<A>(self, F: impl FnOnce(Self::Output) -> A) -> A
fn create(self) -> Self::Output
Box {
orientation: Orientation::HORIZONTAL,
padding: 6,
spacing: 6,
..Default::default()
}.build(|w| { // creations with lambdas use the "build" method
Button {
text: "Hello World",
..w.child()
}.create(); // creations without arguments use the "create" method
})
Current Problems and Future Plans
- Allow the user to define custom default states for templates
- Create a
Templatable
trait associates the Output of a template with its template, so that you don't have to associate it yourself. Eg.:
trait Templatable {
type New: Template<Output = Self>;
}
// ...so that
use some_other_lib::Box;
Box::New {
//...
} ();
- Currently some type analysers do not consider
std::ops::FnOnce
implementations, so they automatically labelStruct { /*...*/ } ( /*...*/ )
syntaxes as wrong when they are perfectly legal and fine as per thestd::ops::FnOnce
trait definition. For example, in the jetbrains Rust Plugin. Until such problems are solved, either use a different analyser, or use the alternative.build()
and.create()
methods.
Dependencies
~7KB