You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Feature Request: Support Iterator<Item=Cow<'static, str>> #508
Somewhat related to #491, but not exactly the same.
Streaming output is nice, but there's an easy middle-ground that works well when you're rendering templates that are mostly static, but have some dynamic parts. If you can shove everything into a Vec<Cow<'static, str>>, you only have to keep the dynamic parts in memory, and the rest can be references to &'static str fragments.
I'm using this in Axum with a custom type that implements IntoResponse, and it's working well. (I'll attach the code in a comment below in case it's useful to anyone.)
It would be great if maud's Render had a way to get an Iterator<Item=Cow<'static, str>> which would let me easily slot it into that system.
Not a blocker for me at all, though. Maud's great, thanks for this library! <3
Example of using Cow<'static, str> in an Axum Response.
use std::{borrow::Cow, convert::Infallible};use axum::{
body::{Body,Bytes},
http::{HeaderMap,HeaderValue,
header::{CONTENT_LENGTH,CONTENT_TYPE},},
response::{IntoResponse,Response},};use tokio_stream::StreamExtas _;/// Efficient body that concatenates string parts as they are output to the client./// (vs. requiring string concatenation in memory.)////// Sets Content-Length based on the length of all string parts./// Sets Conent-Type depending on which constructor was used.#[derive(Debug)]pubstructBodyParts{existing_headers:Option<HeaderMap>,content_type:Option<HeaderValue>,parts:Vec<CowStr>,}implBodyParts{/// Init & set the Content-Type to `text/html; charset=UTF-8`pubfnhtml() -> Self{Self{existing_headers:None,content_type:Some(HeaderValue::from_static("text/html;charset=UTF-8")),parts:Vec::new(),}}/// Add some pre-existing headers to our output Response./// Content-Type and -Length will be overwritten.pubfnwith_existing_headers(mutself,headers:HeaderMap) -> Self{self.existing_headers = Some(headers);self}/// Add some string output.pubfnpush(&mutself,value:implInto<Cow<'static,str>>){self.parts.push(value.into().into());}}implIntoResponseforBodyParts{fninto_response(self) -> Response{letSelf{
existing_headers,
content_type,
parts,} = self;let content_length:usize = parts.iter().map(|it| it.inner.as_bytes().len()).sum();let parts = tokio_stream::iter(parts).map(always_ok);let body = Body::from_stream(parts);letmut response = Response::new(body);ifletSome(headers) = existing_headers {*response.headers_mut() = headers;}ifletSome(content_type) = content_type {
response.headers_mut().insert(CONTENT_TYPE, content_type);}
response
.headers_mut().insert(CONTENT_LENGTH, content_length.into());
response
}}fnalways_ok<T>(value:T) -> Result<T,Infallible>{Ok(value)}// Axum requires that Stream::Item impl Into<Bytes>.// Bytes impls From<'static str> and From<String> but not From<Cow<'static, str>>,// so we have to write our own.// See: https://github.com/tokio-rs/bytes/issues/861#[derive(Debug)]structCowStr{inner:Cow<'static,str>,}implFrom<Cow<'static,str>>forCowStr{fnfrom(value:Cow<'static,str>) -> Self{Self{inner: value }}}implInto<Bytes>forCowStr{fninto(self) -> Bytes{matchself.inner{Cow::Borrowed(static_str) => Bytes::from(static_str),Cow::Owned(owned_string) => Bytes::from(owned_string),}}}
Relevant to the other issue I mentioned, under the hood this does turn things into a Stream. But, it's all handled by the IntoResponse implementation. It's not as efficient as rolling your own, since we DO have to keep all of the dynamic bits in memory. But it's a nice compromise for usability.
🤔 I suppose it might be even more efficient if we let it take Box<dyn Display>, then even more formatting could be postponed. But you get the gist, I hope. :)
Activity
NfNitLoop commentedon Oct 22, 2026
Example of using
Cow<'static, str>in an AxumResponse.Relevant to the other issue I mentioned, under the hood this does turn things into a
Stream. But, it's all handled by theIntoResponseimplementation. It's not as efficient as rolling your own, since we DO have to keep all of the dynamic bits in memory. But it's a nice compromise for usability.🤔 I suppose it might be even more efficient if we let it take
Box<dyn Display>, then even more formatting could be postponed. But you get the gist, I hope. :)