Oh well. It looks like I tried to implement an asynchronous template system for Rust, but instead of actual templates I ended up manually writing a stream of Bytes instances. It takes 10 times as much space as its output. But it’s asynchronous, and I expect that TTFB for, say, a large feed, would be much better with it.

For completeness, here is one template I wrote. It should’ve been an h-card.

pub fn card(mut mf2: serde_json::Value) -> impl Stream<Item = Bytes> + Unpin + Send {
    let uid: String = match mf2["properties"]["uid"][0].take() {
        serde_json::Value::String(uid) => uid,
        _ => panic!("h-card without a uid")
    let photo: Option<String> = match mf2["properties"]["photo"][0].take() {
        serde_json::Value::String(url) => Some(url),
        _ => None
    let name: String = match mf2["properties"]["name"][0].take() {
        serde_json::Value::String(name) => name,
        _ => panic!("encountered h-card without a name")
    chunk(b"<article class=\"h-card\">")
            match photo {
                Some(url) => {
                    let mut tag = Vec::new();
                    tag.extend_from_slice(b"<img class=\"u-photo\" src=\"");
                    html_escape::encode_double_quoted_attribute_to_vec(url, &mut tag);
                    tag.extend_from_slice(b"\" />");

                None => Bytes::new()
            let mut buf = Vec::new();
            buf.extend_from_slice(b"<h1><a class=\"u-url u-uid p-name\" href=\"");
            html_escape::encode_double_quoted_attribute_to_vec(uid, &mut buf);

            html_escape::encode_text_to_vec(&name, &mut buf);



It’s huge. Here is the output it should produce (whitespace is mine):

<article class="h-card">
    <img class="u-photo" src="https://example.com/media/me.png" />
    <h1><a class="u-url u-uid p-name" href="https://example.com/">Jane Doe</a></h1>

I need some sort of macro system to work with these. The idea itself seems good, but the implementation... meh.