From df84ebc0eb00580804bce828af653558668e4070 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Fri, 3 Jul 2026 13:07:42 +0200 Subject: [PATCH 1/3] Add no_std support --- src/ast.rs | 2 ++ src/error.rs | 7 ++++--- src/format.rs | 6 +++++- src/lib.rs | 10 ++++++++++ src/macros.rs | 8 ++++---- src/parser.rs | 1 + src/renderer.rs | 3 ++- src/template.rs | 6 +++++- src/value.rs | 2 +- 9 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 5dc7452..9be6a95 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,5 +1,7 @@ //! Typed AST for parsed format strings. +use alloc::vec::Vec; + /// Byte range in the source format string. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Span { diff --git a/src/error.rs b/src/error.rs index 199b31b..e70abcd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,8 @@ //! Error types for formatx. use crate::ast::{FormatType, Span}; -use std::fmt; +use alloc::string::String; +use core::fmt; /// Errors that can occur during parsing or formatting. #[derive(Debug)] @@ -52,8 +53,8 @@ impl fmt::Display for Error { } } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { if let Self::Format(e) = self { Some(e) } else { diff --git a/src/format.rs b/src/format.rs index eae70b1..32d38ea 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,7 +1,11 @@ //! Format engine - applies [`FormatSpec`] to produce formatted output. use crate::{ast::*, error::Error, value::FormatValue}; -use std::fmt::{Debug, Write}; +use alloc::{ + format, + string::{String, ToString}, +}; +use core::fmt::{Debug, Write}; /// Render a parsed [`FormatString`] into `output` using the provided arguments. pub fn render( diff --git a/src/lib.rs b/src/lib.rs index f7cd04e..d810d49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(test), no_std)] + //! `formatx` lets you format strings at runtime using the same syntax as //! [`std::fmt`] (`{}`, `{:?}`, `{name}`, etc.), but with runtime template //! strings instead of compile-time literals with **zero** dependencies. @@ -42,6 +44,8 @@ //! assert_eq!(r2, "Bob has 7 items"); //! ``` +extern crate alloc; + mod ast; mod error; mod format; @@ -51,6 +55,12 @@ mod renderer; mod template; mod value; +#[doc(hidden)] +pub mod __private { + pub use alloc::string::String; + pub use core::result::Result; +} + pub use ast::FormatType; pub use error::Error; pub use renderer::Renderer; diff --git a/src/macros.rs b/src/macros.rs index 6284308..83cbc88 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -21,12 +21,12 @@ #[macro_export] macro_rules! formatx { ($template:expr $(,)?) => { - (|| -> ::std::result::Result<::std::string::String, $crate::Error> { + (|| -> $crate::__private::Result<$crate::__private::String, $crate::Error> { $crate::Template::new($template)?.render().finish() })() }; ($template:expr, $($args:tt)*) => { - (|| -> ::std::result::Result<::std::string::String, $crate::Error> { + (|| -> $crate::__private::Result<$crate::__private::String, $crate::Error> { let t = $crate::Template::new($template)?; let mut r = t.render(); $crate::_formatx_internal!(r, $($args)*); @@ -52,12 +52,12 @@ macro_rules! formatx { #[macro_export] macro_rules! formatxl { ($template:expr $(,)?) => { - (|| -> ::std::result::Result<::std::string::String, $crate::Error> { + (|| -> $crate::__private::Result<$crate::__private::String, $crate::Error> { $crate::Template::new($template)?.render().finish_lenient() })() }; ($template:expr, $($args:tt)*) => { - (|| -> ::std::result::Result<::std::string::String, $crate::Error> { + (|| -> $crate::__private::Result<$crate::__private::String, $crate::Error> { let t = $crate::Template::new($template)?; let mut r = t.render(); $crate::_formatx_internal!(r, $($args)*); diff --git a/src/parser.rs b/src/parser.rs index f027bf7..890c067 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,7 @@ //! Single-pass parser for `std::fmt` - style format strings. use crate::{ast::*, error::Error}; +use alloc::{format, string::ToString, vec::Vec}; /// Parse a format string into a [`FormatString`] AST. pub fn parse(source: &str) -> Result { diff --git a/src/renderer.rs b/src/renderer.rs index 3bf206e..1a87f2a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,7 +1,8 @@ //! The [`Renderer`] builder - collects arguments and produces formatted output. use crate::{error::Error, format, template::Template, value::FormatValue}; -use std::fmt::{Debug, Display}; +use alloc::{string::String, vec::Vec}; +use core::fmt::{Debug, Display}; /// A builder for rendering a [`Template`] with arguments. /// diff --git a/src/template.rs b/src/template.rs index 7ad169b..974aa3e 100644 --- a/src/template.rs +++ b/src/template.rs @@ -6,7 +6,11 @@ use crate::{ parser, renderer::Renderer, }; -use std::{fmt, str::FromStr}; +use alloc::{ + string::String, + vec::Vec, +}; +use core::{fmt, str::FromStr}; /// An owned, parsed format string that can be rendered many times with different arguments. /// diff --git a/src/value.rs b/src/value.rs index 88fce13..b945756 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,6 @@ //! The [`FormatValue`] marker trait. -use std::fmt::{Debug, Display}; +use core::fmt::{Debug, Display}; /// Marker trait for values that can be formatted at runtime. /// From ea7f12040dc8f9cdba2b09da32f8056d0bd31139 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Fri, 3 Jul 2026 14:07:43 +0200 Subject: [PATCH 2/3] improve trait bounds for &str remove unnecessery comments --- examples/simple.rs | 2 +- examples/struct.rs | 6 ++++-- src/error.rs | 15 +++----------- src/format.rs | 50 ++++++++++++++++++++++++++++++++-------------- src/lib.rs | 2 +- src/renderer.rs | 18 ++++++++++------- src/template.rs | 5 +---- src/value.rs | 49 ++++++++++++++++++++++++++++++++++++++++----- 8 files changed, 100 insertions(+), 47 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 094d503..8c5e2fa 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -18,4 +18,4 @@ fn main() { // Debug formatting let result = formatx!("{:?}", "a string").unwrap(); println!("{result}"); -} +} \ No newline at end of file diff --git a/examples/struct.rs b/examples/struct.rs index 927023e..07cb953 100644 --- a/examples/struct.rs +++ b/examples/struct.rs @@ -30,12 +30,14 @@ fn main() { // Template reuse let template = formatx::Template::new("Point {name}: {point}").unwrap(); - let r1 = template.render() + let r1 = template + .render() .named("name", &"Origin") .named("point", &origin) .finish() .unwrap(); - let r2 = template.render() + let r2 = template + .render() .named("name", &"Target") .named("point", &target) .finish() diff --git a/src/error.rs b/src/error.rs index e70abcd..0bb97cf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,20 +8,11 @@ use core::fmt; #[derive(Debug)] pub enum Error { /// The format string could not be parsed. - Parse { - span: Span, - message: String, - }, + Parse { span: Span, message: String }, /// A placeholder references an argument that was not provided. - MissingArgument { - name: String, - span: Span, - }, + MissingArgument { name: String, span: Span }, /// A format type (e.g. `{:x}`) requires a trait we don't support. - UnsupportedTrait { - format_type: FormatType, - span: Span, - }, + UnsupportedTrait { format_type: FormatType, span: Span }, /// An underlying `std::fmt::Error` occurred during formatting. Format(fmt::Error), } diff --git a/src/format.rs b/src/format.rs index 32d38ea..b9fbf15 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,6 +1,10 @@ //! Format engine - applies [`FormatSpec`] to produce formatted output. -use crate::{ast::*, error::Error, value::FormatValue}; +use crate::{ + ast::*, + error::Error, + value::{FormatArg, FormatValue}, +}; use alloc::{ format, string::{String, ToString}, @@ -12,7 +16,7 @@ pub fn render( output: &mut String, source: &str, parsed: &FormatString, - args: &[&dyn FormatValue], + args: &[FormatArg], named: &[(&str, usize)], strict: bool, ) -> Result<(), Error> { @@ -40,7 +44,7 @@ pub fn render( resolve_argument(&placeholder.argument, source, &mut implicit_pos, named); let arg = match arg_index { - Some(idx) if idx < args.len() => Some(args[idx]), + Some(idx) if idx < args.len() => Some(args[idx].as_value()), Some(_) | None => None, }; @@ -125,7 +129,7 @@ fn resolve_argument( fn resolve_count_value( count: &Option, source: &str, - args: &[&dyn FormatValue], + args: &[FormatArg], named: &[(&str, usize)], ) -> Result, Error> { let Some(count) = count else { return Ok(None) }; @@ -152,7 +156,7 @@ fn resolve_count_value( message: format!("count argument index {idx} out of range"), }); } - let formatted = format!("{}", args[idx]); + let formatted = format!("{}", args[idx].as_value()); formatted .parse::() .map(Some) @@ -167,7 +171,7 @@ fn resolve_count_value( fn resolve_precision( precision: &Option, source: &str, - args: &[&dyn FormatValue], + args: &[FormatArg], named: &[(&str, usize)], implicit_pos: &mut usize, ) -> Result, Error> { @@ -185,7 +189,7 @@ fn resolve_precision( message: "not enough arguments for `.*` precision".to_string(), }); } - let formatted = format!("{}", args[idx]); + let formatted = format!("{}", args[idx].as_value()); formatted .parse::() .map(Some) @@ -296,10 +300,18 @@ fn format_full( (true, false, _, None, Some(p)) => write!(output, "{:+.prec$?}", dbg, prec = p), (false, true, _, None, Some(p)) => write!(output, "{:#.prec$?}", dbg, prec = p), (true, true, _, None, Some(p)) => write!(output, "{:+#.prec$?}", dbg, prec = p), - (false, false, false, Some(w), None) => write!(output, "{:width$?}", dbg, width = w), - (true, false, false, Some(w), None) => write!(output, "{:+width$?}", dbg, width = w), - (false, true, false, Some(w), None) => write!(output, "{:#width$?}", dbg, width = w), - (true, true, false, Some(w), None) => write!(output, "{:+#width$?}", dbg, width = w), + (false, false, false, Some(w), None) => { + write!(output, "{:width$?}", dbg, width = w) + } + (true, false, false, Some(w), None) => { + write!(output, "{:+width$?}", dbg, width = w) + } + (false, true, false, Some(w), None) => { + write!(output, "{:#width$?}", dbg, width = w) + } + (true, true, false, Some(w), None) => { + write!(output, "{:+#width$?}", dbg, width = w) + } (false, false, false, Some(w), Some(p)) => { write!(output, "{:width$.prec$?}", dbg, width = w, prec = p) } @@ -312,10 +324,18 @@ fn format_full( (true, true, false, Some(w), Some(p)) => { write!(output, "{:+#width$.prec$?}", dbg, width = w, prec = p) } - (false, false, true, Some(w), None) => write!(output, "{:0width$?}", dbg, width = w), - (true, false, true, Some(w), None) => write!(output, "{:+0width$?}", dbg, width = w), - (false, true, true, Some(w), None) => write!(output, "{:#0width$?}", dbg, width = w), - (true, true, true, Some(w), None) => write!(output, "{:+#0width$?}", dbg, width = w), + (false, false, true, Some(w), None) => { + write!(output, "{:0width$?}", dbg, width = w) + } + (true, false, true, Some(w), None) => { + write!(output, "{:+0width$?}", dbg, width = w) + } + (false, true, true, Some(w), None) => { + write!(output, "{:#0width$?}", dbg, width = w) + } + (true, true, true, Some(w), None) => { + write!(output, "{:+#0width$?}", dbg, width = w) + } (false, false, true, Some(w), Some(p)) => { write!(output, "{:0width$.prec$?}", dbg, width = w, prec = p) } diff --git a/src/lib.rs b/src/lib.rs index d810d49..5026094 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,4 +65,4 @@ pub use ast::FormatType; pub use error::Error; pub use renderer::Renderer; pub use template::Template; -pub use value::FormatValue; +pub use value::{FormatArg, FormatValue, IntoFormatArg}; diff --git a/src/renderer.rs b/src/renderer.rs index 1a87f2a..12408d1 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,8 +1,12 @@ //! The [`Renderer`] builder - collects arguments and produces formatted output. -use crate::{error::Error, format, template::Template, value::FormatValue}; +use crate::{ + error::Error, + format, + template::Template, + value::{FormatArg, IntoFormatArg}, +}; use alloc::{string::String, vec::Vec}; -use core::fmt::{Debug, Display}; /// A builder for rendering a [`Template`] with arguments. /// @@ -22,7 +26,7 @@ use core::fmt::{Debug, Display}; /// ``` pub struct Renderer<'a> { template: &'a Template, - args: Vec<&'a dyn FormatValue>, + args: Vec>, named: Vec<(&'a str, usize)>, } @@ -38,16 +42,16 @@ impl<'a> Renderer<'a> { /// Add a positional argument. #[inline] - pub fn arg(&mut self, value: &'a (impl Display + Debug)) -> &mut Self { - self.args.push(value as &dyn FormatValue); + pub fn arg(&mut self, value: impl IntoFormatArg<'a>) -> &mut Self { + self.args.push(value.into_format_arg()); self } /// Add a named argument. #[inline] - pub fn named(&mut self, name: &'a str, value: &'a (impl Display + Debug)) -> &mut Self { + pub fn named(&mut self, name: &'a str, value: impl IntoFormatArg<'a>) -> &mut Self { let index = self.args.len(); - self.args.push(value as &dyn FormatValue); + self.args.push(value.into_format_arg()); self.named.push((name, index)); self } diff --git a/src/template.rs b/src/template.rs index 974aa3e..bf500b2 100644 --- a/src/template.rs +++ b/src/template.rs @@ -6,10 +6,7 @@ use crate::{ parser, renderer::Renderer, }; -use alloc::{ - string::String, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use core::{fmt, str::FromStr}; /// An owned, parsed format string that can be rendered many times with different arguments. diff --git a/src/value.rs b/src/value.rs index b945756..c482e18 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,12 +1,51 @@ -//! The [`FormatValue`] marker trait. +//! The [`FormatValue`] marker trait and zero-allocation argument storage. use core::fmt::{Debug, Display}; /// Marker trait for values that can be formatted at runtime. /// -/// Blanket-implemented for all `T: Display + Debug`, which covers the vast -/// majority of Rust types (`i32`, `f64`, `String`, `&str`, `bool`, `char`, -/// custom types with `#[derive(Debug)]` and a `Display` impl, etc.). +/// Blanket-implemented for all `T: Display + Debug` (including unsized types +/// such as `str`), which covers the vast majority of Rust types (`i32`, `f64`, +/// `String`, `&str`, `bool`, `char`, custom types with `#[derive(Debug)]` and a +/// `Display` impl, etc.). pub trait FormatValue: Display + Debug {} -impl FormatValue for T {} +impl FormatValue for T {} + +pub enum FormatArg<'a> { + /// Any `Sized` value borrowed as a trait object. + Dyn(&'a dyn FormatValue), + /// A string slice, stored by value. + Str(&'a str), +} + +impl<'a> FormatArg<'a> { + /// Borrow this argument as a `&dyn FormatValue` for formatting. + #[inline] + pub(crate) fn as_value(&self) -> &dyn FormatValue { + match self { + FormatArg::Dyn(value) => *value, + FormatArg::Str(s) => s, + } + } +} + +/// Conversion into a [`FormatArg`] without allocation. +pub trait IntoFormatArg<'a> { + /// Convert `self` into a [`FormatArg`]. + fn into_format_arg(self) -> FormatArg<'a>; +} + +impl<'a> IntoFormatArg<'a> for &'a str { + #[inline] + fn into_format_arg(self) -> FormatArg<'a> { + FormatArg::Str(self) + } +} + +impl<'a, T: Display + Debug> IntoFormatArg<'a> for &'a T { + #[inline] + fn into_format_arg(self) -> FormatArg<'a> { + FormatArg::Dyn(self) + } +} From c22094b67d456b11bdf8e438996f6b82c23088aa Mon Sep 17 00:00:00 2001 From: Fabrice Date: Fri, 3 Jul 2026 15:38:06 +0200 Subject: [PATCH 3/3] some type cleanup remove copy of input string cleanness adjustments remove memory measurement --- images/benchmark.svg | 40 ++++++++++++++++++++-------------------- src/ast.rs | 16 +++++----------- src/format.rs | 10 ++++++---- src/renderer.rs | 8 ++++---- src/template.rs | 27 ++++++++++++--------------- tests/integration.rs | 6 ------ 6 files changed, 47 insertions(+), 60 deletions(-) diff --git a/images/benchmark.svg b/images/benchmark.svg index c3d5678..d3bc5a1 100644 --- a/images/benchmark.svg +++ b/images/benchmark.svg @@ -8,32 +8,32 @@ 0 -87 +56 -174 +111 -261 +167 -348 - -27 ns - -250 ns +223 + +19 ns + +194 ns {} + {} = {} - -28 ns - -229 ns + +16 ns + +164 ns {name} scored {score}% - -97 ns - -223 ns + +80 ns + +172 ns {:+08.2} - -149 ns - -303 ns + +104 ns + +183 ns {:-^20} Expected overhead - formatx! parses and resolves format strings at runtime. diff --git a/src/ast.rs b/src/ast.rs index 9be6a95..e69b22c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -53,7 +53,7 @@ pub enum Argument { } /// The full format specification after `:` inside a placeholder. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct FormatSpec { pub fill: Option, pub align: Option, @@ -84,13 +84,7 @@ impl FormatSpec { /// meaning we can take the fast path and `write!` directly. #[inline] pub fn is_default(&self) -> bool { - self.fill.is_none() - && self.align.is_none() - && self.sign.is_none() - && !self.alternate - && !self.zero_pad - && self.width.is_none() - && self.precision.is_none() + self == &Self::default() } } @@ -110,7 +104,7 @@ pub enum Sign { } /// A width or precision value - either a literal number or a parameter reference. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Count { /// A literal integer, e.g. `10` in `{:10}`. Literal(usize), @@ -119,7 +113,7 @@ pub enum Count { } /// A parameter reference for width/precision. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum CountParam { /// `{:0$}` - positional argument index. Positional(usize), @@ -128,7 +122,7 @@ pub enum CountParam { } /// Precision specification. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Precision { /// `.5` or `.prec$` - a count value. Count(Count), diff --git a/src/format.rs b/src/format.rs index b9fbf15..b213fd4 100644 --- a/src/format.rs +++ b/src/format.rs @@ -11,13 +11,15 @@ use alloc::{ }; use core::fmt::{Debug, Write}; +type NamedArgs<'a> = [(&'a str, usize)]; + /// Render a parsed [`FormatString`] into `output` using the provided arguments. pub fn render( output: &mut String, source: &str, parsed: &FormatString, args: &[FormatArg], - named: &[(&str, usize)], + named: &NamedArgs, strict: bool, ) -> Result<(), Error> { let mut implicit_pos: usize = 0; @@ -110,7 +112,7 @@ fn resolve_argument( argument: &Argument, source: &str, implicit_pos: &mut usize, - named: &[(&str, usize)], + named: &NamedArgs, ) -> Option { match argument { Argument::Implicit => { @@ -130,7 +132,7 @@ fn resolve_count_value( count: &Option, source: &str, args: &[FormatArg], - named: &[(&str, usize)], + named: &NamedArgs, ) -> Result, Error> { let Some(count) = count else { return Ok(None) }; match count { @@ -172,7 +174,7 @@ fn resolve_precision( precision: &Option, source: &str, args: &[FormatArg], - named: &[(&str, usize)], + named: &NamedArgs, implicit_pos: &mut usize, ) -> Result, Error> { let Some(prec) = precision else { diff --git a/src/renderer.rs b/src/renderer.rs index 12408d1..8c0544e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -25,14 +25,14 @@ use alloc::{string::String, vec::Vec}; /// assert_eq!(result, "1 + 2 = 3"); /// ``` pub struct Renderer<'a> { - template: &'a Template, + template: &'a Template<'a>, args: Vec>, named: Vec<(&'a str, usize)>, } impl<'a> Renderer<'a> { /// Create a new renderer for the given template. - pub(crate) fn new(template: &'a Template) -> Self { + pub(crate) fn new(template: &'a Template<'a>) -> Self { Self { template, args: Vec::new(), @@ -42,14 +42,14 @@ impl<'a> Renderer<'a> { /// Add a positional argument. #[inline] - pub fn arg(&mut self, value: impl IntoFormatArg<'a>) -> &mut Self { + pub fn arg>(&mut self, value: V) -> &mut Self { self.args.push(value.into_format_arg()); self } /// Add a named argument. #[inline] - pub fn named(&mut self, name: &'a str, value: impl IntoFormatArg<'a>) -> &mut Self { + pub fn named>(&mut self, name: &'a str, value: V) -> &mut Self { let index = self.args.len(); self.args.push(value.into_format_arg()); self.named.push((name, index)); diff --git a/src/template.rs b/src/template.rs index bf500b2..349b18e 100644 --- a/src/template.rs +++ b/src/template.rs @@ -6,8 +6,8 @@ use crate::{ parser, renderer::Renderer, }; -use alloc::{string::String, vec::Vec}; -use core::{fmt, str::FromStr}; +use alloc::vec::Vec; +use core::fmt; /// An owned, parsed format string that can be rendered many times with different arguments. /// @@ -26,18 +26,17 @@ use core::{fmt, str::FromStr}; /// .unwrap(); /// assert_eq!(result, "Alice scored 95.7%"); /// ``` -pub struct Template { - source: String, +pub struct Template<'a> { + source: &'a str, parsed: FormatString, } -impl Template { +impl<'a> Template<'a> { /// Parse a format string into a reusable template. /// /// Returns `Err` if the format string is malformed (unmatched braces, invalid specs, etc.). - pub fn new>(source: S) -> Result { - let source = source.into(); - let parsed = parser::parse(&source)?; + pub fn new(source: &'a str) -> Result { + let parsed = parser::parse(source)?; Ok(Self { source, parsed }) } @@ -92,13 +91,13 @@ impl Template { } } -impl fmt::Display for Template { +impl<'a> fmt::Display for Template<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.source) } } -impl fmt::Debug for Template { +impl<'a> fmt::Debug for Template<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Template") .field("source", &self.source) @@ -106,10 +105,8 @@ impl fmt::Debug for Template { } } -impl FromStr for Template { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::new(s) +impl<'a> From<&'a str> for Template<'a> { + fn from(source: &'a str) -> Self { + Self::new(source).unwrap() } } diff --git a/tests/integration.rs b/tests/integration.rs index 394a26f..36ce7ef 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -223,12 +223,6 @@ fn template_placeholders() { assert_eq!(t.placeholders(), vec!["name", "score", "name"]); } -#[test] -fn template_from_str() { - let t: Template = "{:?}".parse().unwrap(); - assert_eq!(t.render().arg(&42).finish().unwrap(), "42"); -} - #[test] fn unicode_fill() { assert_fmt!("{:★>10}", "hi");