From 92e3bf88b53ddffe0c89eba7d6e1bf12f2749b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 16 Sep 2024 17:11:44 +0200 Subject: [PATCH] Add warning about slow `.to_string()` method --- book/src/performance.md | 24 ++++++++++++++++++++++++ rinja/src/lib.rs | 18 ++++++++++++++++++ rinja_derive/src/generator.rs | 10 ++++++++++ rinja_derive/src/tests.rs | 3 +++ 4 files changed, 55 insertions(+) diff --git a/book/src/performance.md b/book/src/performance.md index d28b5f52..b2f27e3c 100644 --- a/book/src/performance.md +++ b/book/src/performance.md @@ -1,5 +1,29 @@ # Performance +## Rendering Performance + +When rendering a rinja template, you should prefer the methods + +* [`.render()`] (to render the content into a new string), +* [`.render_into()`] (to render the content into an [`fmt::Write`] object, e.g. [`String`]) or +* [`.write_into()`] (to render the content into an [`io::Write`] object, e.g. [`Vec`]) + +over [`.to_string()`] or [`format!()`]. +While `.to_string()` and `format!()` give you the same result, they generally perform much worse +than rinja's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of +monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than `.render()`. + +[dynamic methods calls]: +[`.render()`]: +[`.render_into()`]: +[`.write_into()`]: +[`fmt::Write`]: +[`String`]: +[`io::Write`]: +[`Vec`]: +[`.to_string()`]: +[`format!()`]: + ## Slow Debug Recompilations If you experience slow compile times when iterating with lots of templates, diff --git a/rinja/src/lib.rs b/rinja/src/lib.rs index 718b15aa..5bd4e621 100644 --- a/rinja/src/lib.rs +++ b/rinja/src/lib.rs @@ -75,6 +75,24 @@ pub use crate::error::{Error, Result}; /// Main `Template` trait; implementations are generally derived /// /// If you need an object-safe template, use [`DynTemplate`]. +/// +/// ## Rendering performance +/// +/// When rendering a rinja template, you should prefer the methods +/// +/// * [`.render()`][Template::render] (to render the content into a new string), +/// * [`.render_into()`][Template::render_into] (to render the content into an [`fmt::Write`] +/// object, e.g. [`String`]) or +/// * [`.write_into()`][Template::write_into] (to render the content into an [`io::Write`] object, +/// e.g. [`Vec`]) +/// +/// over [`.to_string()`][std::string::ToString::to_string] or [`format!()`]. +/// While `.to_string()` and `format!()` give you the same result, they generally perform much worse +/// than rinja's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of +/// monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than +/// `.render()`. +/// +/// [dynamic methods calls]: pub trait Template: fmt::Display { /// Helper method which allocates a new `String` and renders into it fn render(&self) -> Result { diff --git a/rinja_derive/src/generator.rs b/rinja_derive/src/generator.rs index 3b0a2ff6..cc16af51 100644 --- a/rinja_derive/src/generator.rs +++ b/rinja_derive/src/generator.rs @@ -174,6 +174,16 @@ impl<'a> Generator<'a> { // Implement `Display` for the given context struct. fn impl_display(&mut self, buf: &mut Buffer) { + let ident = &self.input.ast.ident; + buf.write(format_args!( + "\ + /// Implement the [`format!()`][::std::format] trait for [`{}`]\n\ + ///\n\ + /// Please be aware of the rendering performance notice in the \ + [`Template`][{CRATE}::Template] trait.\n\ + ", + quote!(#ident), + )); self.write_header(buf, "::core::fmt::Display", None); buf.write(format_args!( "\ diff --git a/rinja_derive/src/tests.rs b/rinja_derive/src/tests.rs index ce957ceb..3e4fb500 100644 --- a/rinja_derive/src/tests.rs +++ b/rinja_derive/src/tests.rs @@ -55,6 +55,9 @@ struct Foo {{ {} }}"##, const SIZE_HINT: ::core::primitive::usize = #size_hint; const MIME_TYPE: &'static ::core::primitive::str = "text/plain; charset=utf-8"; } + /// Implement the [`format!()`][::std::format] trait for [`Foo`] + /// + /// Please be aware of the rendering performance notice in the [`Template`][::rinja::Template] trait. impl ::core::fmt::Display for Foo { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {