book: add page about FastWritable

This commit is contained in:
René Kijewski 2025-04-19 18:57:08 +02:00
parent 7881bc131b
commit 94fddd4df5
2 changed files with 73 additions and 0 deletions

View File

@ -24,6 +24,27 @@ monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower
[`.to_string()`]: <https://doc.rust-lang.org/stable/std/string/trait.ToString.html#tymethod.to_string>
[`format!()`]: <https://doc.rust-lang.org/stable/std/fmt/fn.format.html>
## Faster Rendering of Custom Types
Every type that implements [`fmt::Display`] can be used in askama expressions: `{{ value }}`.
Rendering with `fmt::Display` can be slow, though, because it uses [dynamic methods calls] in its
[`fmt::Formatter`] argument. To speed up rendering (by a lot, actually),
askama adds the trait [`FastWritable`]. For any custom type you want to render,
it has to implement `fmt::Display`, but if it also implements `FastWritable`,
then using [autoref-based specialization] the latter implementation is automatically preferred.
To reduce the amount of code duplication, you can let your `fmt::Display` implementation call
your `FastWritable` implementation:
```rust
{{#include ../../testing/tests/book_example_performance_fmt_call_fast_writable.rs}}
```
[`fmt::Display`]: <https://doc.rust-lang.org/stable/std/fmt/trait.Display.html>
[`fmt::Formatter`]: <https://doc.rust-lang.org/stable/std/fmt/struct.Formatter.html>
[`FastWritable`]: <./doc/askama/filters/trait.FastWritable.html>
[autoref-based specialization]: <https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html>
## Slow Debug Recompilations
If you experience slow compile times when iterating with lots of templates,

View File

@ -0,0 +1,52 @@
use std::fmt::{self, Write};
use askama::NO_VALUES;
use askama::filters::FastWritable;
// In a real application, please have a look at
// https://github.com/kdeldycke/awesome-falsehood/blob/690a070/readme.md#human-identity
struct Name<'a> {
forename: &'a str,
surname: &'a str,
}
impl fmt::Display for Name<'_> {
// Because the method simply forwards the call, it should be `inline`.
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// `fmt::Write` has no access to runtime values,
// so simply pass `NO_VALUES`.
self.write_into(f, NO_VALUES)?;
Ok(())
}
}
impl FastWritable for Name<'_> {
fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_values: &dyn askama::Values,
) -> askama::Result<()> {
dest.write_str(self.surname)?;
dest.write_str(", ")?;
dest.write_str(self.forename)?;
Ok(())
}
}
#[test]
fn both_implementations_should_render_the_same_text() {
let person = Name {
forename: "Max",
surname: "Mustermann",
};
let mut buf_fmt = String::new();
write!(buf_fmt, "{person}").unwrap();
let mut buf_fast = String::new();
person.write_into(&mut buf_fast, NO_VALUES).unwrap();
assert_eq!(buf_fmt, buf_fast);
assert_eq!(buf_fmt, "Mustermann, Max");
}