diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4aa663..2d036c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +- Remove blanket `Render` impl for `T: Display` + [#320](https://github.com/lambda-fairy/maud/pull/320) + ## [0.23.0] - 2021-11-10 - Update to support axum 0.2 diff --git a/docs/Cargo.lock b/docs/Cargo.lock index ba73376c..cb43050a 100644 --- a/docs/Cargo.lock +++ b/docs/Cargo.lock @@ -326,6 +326,7 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" name = "maud" version = "0.23.0" dependencies = [ + "itoa", "maud_macros", ] diff --git a/docs/content/render-trait.md b/docs/content/render-trait.md index 0ee4ddff..e49b73ab 100644 --- a/docs/content/render-trait.md +++ b/docs/content/render-trait.md @@ -1,15 +1,9 @@ # The `Render` trait -By default, -a [`(splice)`](splices-toggles.md) is rendered using the [`std::fmt::Display`][Display] trait, -with any HTML special characters escaped automatically. +Maud uses the [`Render`][Render] trait to convert [`(spliced)`](splices-toggles.md) values to HTML. +This is implemented for many Rust primitive types (`&str`, `i32`) by default, but you can implement it for your own types as well. -To change this behavior, -implement the [`Render`][Render] trait for your type. -Then, when a value of this type is used in a template, -Maud will call your custom code instead. - -Below are some examples of using `Render`. +Below are some examples of implementing `Render`. Feel free to use these snippets in your own project! ## Example: a shorthand for including CSS stylesheets diff --git a/docs/content/splices-toggles.md b/docs/content/splices-toggles.md index 26b646d1..a5fbabd1 100644 --- a/docs/content/splices-toggles.md +++ b/docs/content/splices-toggles.md @@ -94,11 +94,11 @@ html! { ### What can be spliced? -You can splice any value that implements [`std::fmt::Display`][Display]. +You can splice any value that implements [`Render`][Render]. Most primitive types (such as `str` and `i32`) implement this trait, so they should work out of the box. -To change this behavior for some type, +To get this behavior for a custom type, you can implement the [`Render`][Render] trait by hand. The [`PreEscaped`][PreEscaped] wrapper type, which outputs its argument without escaping, @@ -116,7 +116,6 @@ html! { # ; ``` -[Display]: http://doc.rust-lang.org/std/fmt/trait.Display.html [Render]: https://docs.rs/maud/*/maud/trait.Render.html [PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html diff --git a/maud/Cargo.toml b/maud/Cargo.toml index a6c1f025..33c54ba8 100644 --- a/maud/Cargo.toml +++ b/maud/Cargo.toml @@ -19,6 +19,7 @@ actix-web = ["actix-web-dep", "futures-util"] [dependencies] maud_macros = { version = "0.23.0", path = "../maud_macros" } +itoa = { version = "0.4.8", default-features = false, features = ["i128"] } rocket = { version = ">= 0.3, < 0.5", optional = true } futures-util = { version = "0.3.0", optional = true, default-features = false } actix-web-dep = { package = "actix-web", version = ">= 2, < 4", optional = true, default-features = false } diff --git a/maud/src/lib.rs b/maud/src/lib.rs index a40b0c4e..8ff83990 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -11,8 +11,8 @@ extern crate alloc; -use alloc::string::String; -use core::fmt::{self, Write}; +use alloc::{borrow::Cow, boxed::Box, string::String}; +use core::fmt::{self, Arguments, Write}; pub use maud_macros::{html, html_debug}; @@ -58,16 +58,9 @@ impl<'a> fmt::Write for Escaper<'a> { /// Represents a type that can be rendered as HTML. /// -/// If your type implements [`Display`][1], then it will implement this -/// trait automatically through a blanket impl. -/// -/// [1]: https://doc.rust-lang.org/std/fmt/trait.Display.html -/// -/// On the other hand, if your type has a custom HTML representation, -/// then you can implement `Render` by hand. To do this, override -/// either the `.render()` or `.render_to()` methods; since each is -/// defined in terms of the other, you only need to implement one of -/// them. See the example below. +/// To implement this for your own type, override either the `.render()` +/// or `.render_to()` methods; since each is defined in terms of the +/// other, you only need to implement one of them. See the example below. /// /// # Minimal implementation /// @@ -114,48 +107,81 @@ pub trait Render { } } -impl Render for T { +impl Render for str { fn render_to(&self, w: &mut String) { - let _ = write!(Escaper::new(w), "{}", self); + escape::escape_to_string(self, w); } } -/// Spicy hack to specialize `Render` for `T: AsRef`. -/// -/// The `std::fmt` machinery is rather heavyweight, both in code size and speed. -/// It would be nice to skip this overhead for the common cases of `&str` and -/// `String`. But the obvious solution uses *specialization*, which (as of this -/// writing) requires Nightly. The [*inherent method specialization*][1] trick -/// is less clear but works on Stable. -/// -/// This module is an implementation detail and should not be used directly. -/// -/// [1]: https://github.com/dtolnay/case-studies/issues/14 -#[doc(hidden)] -pub mod render { - use crate::{Escaper, Render}; - use alloc::string::String; - use core::fmt::Write; +impl Render for String { + fn render_to(&self, w: &mut String) { + str::render_to(self, w); + } +} - pub trait RenderInternal { - fn __maud_render_to(&self, w: &mut String); +impl<'a> Render for Cow<'a, str> { + fn render_to(&self, w: &mut String) { + str::render_to(self, w); } +} - pub struct RenderWrapper<'a, T: ?Sized>(pub &'a T); +impl<'a> Render for Arguments<'a> { + fn render_to(&self, w: &mut String) { + let _ = Escaper::new(w).write_fmt(*self); + } +} - impl<'a, T: AsRef + ?Sized> RenderWrapper<'a, T> { - pub fn __maud_render_to(&self, w: &mut String) { - let _ = Escaper::new(w).write_str(self.0.as_ref()); - } +impl<'a, T: Render + ?Sized> Render for &'a T { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); } +} - impl<'a, T: Render + ?Sized> RenderInternal for RenderWrapper<'a, T> { - fn __maud_render_to(&self, w: &mut String) { - self.0.render_to(w); - } +impl<'a, T: Render + ?Sized> Render for &'a mut T { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); + } +} + +impl Render for Box { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); } } +macro_rules! impl_render_with_display { + ($($ty:ty)*) => { + $( + impl Render for $ty { + fn render_to(&self, w: &mut String) { + format_args!("{self}").render_to(w); + } + } + )* + }; +} + +impl_render_with_display! { + char f32 f64 +} + +macro_rules! impl_render_with_itoa { + ($($ty:ty)*) => { + $( + impl Render for $ty { + fn render_to(&self, w: &mut String) { + let _ = itoa::fmt(w, *self); + } + } + )* + }; +} + +impl_render_with_itoa! { + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize +} + /// A wrapper that renders the inner value without escaping. #[derive(Debug, Clone, Copy)] pub struct PreEscaped>(pub T); diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs index e6dc9ffc..ad8a0889 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros/src/generate.rs @@ -103,11 +103,7 @@ impl Generator { fn splice(&self, expr: TokenStream, build: &mut Builder) { let output_ident = self.output_ident.clone(); - let tokens = quote!({ - use maud::render::{RenderInternal, RenderWrapper}; - RenderWrapper(&#expr).__maud_render_to(&mut #output_ident); - }); - build.push_tokens(tokens); + build.push_tokens(quote!(maud::Render::render_to(&#expr, &mut #output_ident);)); } fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut Builder) {