diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index c69df18..445c3ba 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -40,25 +40,25 @@ macro_rules! ident {
     ($sp:pat, $x:pat) => (TtToken($sp, token::Ident($x, token::IdentStyle::Plain)))
 }
 
-pub fn parse(cx: &mut ExtCtxt, input: &[TokenTree], sp: Span) -> P<Expr> {
-    Renderer::with(cx, |render| {
-        Parser {
-            in_attr: false,
-            input: input,
-            span: sp,
-            render: render,
-        }.markups();
-    })
+pub fn parse(cx: &ExtCtxt, input: &[TokenTree], sp: Span) -> P<Expr> {
+    let mut render = Renderer::new(cx);
+    Parser {
+        in_attr: false,
+        input: input,
+        span: sp,
+        render: &mut render,
+    }.markups();
+    render.into_expr()
 }
 
-struct Parser<'cx: 'r, 's: 'cx, 'i, 'r, 'o: 'r> {
+struct Parser<'cx: 'r, 's: 'cx, 'i, 'r> {
     in_attr: bool,
     input: &'i [TokenTree],
     span: Span,
-    render: &'r mut Renderer<'cx, 's, 'o>,
+    render: &'r mut Renderer<'cx, 's>,
 }
 
-impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
+impl<'cx, 's, 'i, 'r> Parser<'cx, 's, 'i, 'r> {
     /// Consume `n` items from the input.
     fn shift(&mut self, n: usize) {
         self.input = &self.input[n..];
@@ -221,7 +221,7 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
 }
 
 /// Convert a literal to a string.
-fn lit_to_string(cx: &mut ExtCtxt, lit: Lit, minus: bool) -> Option<String> {
+fn lit_to_string(cx: &ExtCtxt, lit: Lit, minus: bool) -> Option<String> {
     use syntax::ast::Lit_::*;
     let mut result = String::new();
     if minus {
diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs
index 9ed6197..8143835 100644
--- a/maud_macros/src/render.rs
+++ b/maud_macros/src/render.rs
@@ -13,27 +13,23 @@ pub enum Escape {
     Escape,
 }
 
-pub struct Renderer<'cx, 's: 'cx, 'o> {
-    pub cx: &'cx mut ExtCtxt<'s>,
-    stmts: &'o mut Vec<P<Stmt>>,
+pub struct Renderer<'cx, 's: 'cx> {
+    pub cx: &'cx ExtCtxt<'s>,
+    stmts: Vec<P<Stmt>>,
     w: Ident,
 }
 
-impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
-    pub fn with<F>(cx: &'cx mut ExtCtxt<'s>, f: F) -> P<Expr> where
-        F: for<'o_> FnOnce(&mut Renderer<'cx, 's, 'o_>)
-    {
-        let mut stmts = vec![];
-        let w = Ident::new(token::intern("w"));
-        let cx = {
-            let mut render = Renderer {
-                cx: cx,
-                stmts: &mut stmts,
-                w: w,
-            };
-            f(&mut render);
-            render.cx
-        };
+impl<'cx, 's> Renderer<'cx, 's> {
+    pub fn new(cx: &'cx ExtCtxt<'s>) -> Renderer<'cx, 's> {
+        Renderer {
+            cx: cx,
+            stmts: vec![],
+            w: Ident::new(token::intern("w")),
+        }
+    }
+
+    pub fn into_expr(self) -> P<Expr> {
+        let Renderer { cx, stmts, w } = self;
         quote_expr!(cx,
             ::maud::rt::make_markup(|&: $w: &mut ::std::fmt::Writer| -> Result<(), ::std::fmt::Error> {
                 $stmts
@@ -41,22 +37,29 @@ impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
             }))
     }
 
-    /// Push an expression statement, also wrapping it with `try!`.
-    fn push(&mut self, expr: P<Expr>) {
-        let stmt = self.make_stmt(expr);
-        self.stmts.push(stmt);
+    fn make_stmts<F>(&self, f: F) -> Vec<P<Stmt>> where
+        F: FnOnce(&mut Renderer<'cx, 's>)
+    {
+        let mut render = Renderer {
+            cx: self.cx,
+            stmts: vec![],
+            w: self.w,
+        };
+        f(&mut render);
+        render.stmts
     }
 
-    /// Create an expression statement, also wrapping it with `try!`.
-    fn make_stmt(&mut self, expr: P<Expr>) -> P<Stmt> {
-        self.cx.stmt_expr(self.cx.expr_try(expr.span, expr))
+    /// Push an expression statement, also wrapping it with `try!`.
+    fn push_try(&mut self, expr: P<Expr>) {
+        let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
+        self.stmts.push(stmt);
     }
 
     /// Append a literal pre-escaped string.
     fn write(&mut self, s: &str) {
         let w = self.w;
         let expr = quote_expr!(self.cx, $w.write_str($s));
-        self.push(expr);
+        self.push_try(expr);
     }
 
     /// Append a literal string, with the specified escaping method.
@@ -80,7 +83,7 @@ impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
                         &mut ::maud::rt::Escaper { inner: $w },
                         $expr)),
         };
-        self.push(expr);
+        self.push_try(expr);
     }
 
     pub fn element_open_start(&mut self, name: &str) {
@@ -100,21 +103,17 @@ impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
     }
 
     pub fn attribute_empty_if(&mut self, name: &str, expr: P<Expr>) {
-        let s: String = [" ", name].concat();
-        let s = &s[];
-        let w = self.w;
         // Silence "unnecessary parentheses" warnings
         let expr = match expr.node {
             ExprParen(ref inner) => inner.clone(),
             _ => expr.clone(),
         };
-        let expr = quote_expr!(self.cx,
-            if $expr {
-                $w.write_str($s)
-            } else {
-                Ok(())
-            });
-        self.push(expr);
+        let stmts = self.make_stmts(|r| {
+            r.write(" ");
+            r.write(name);
+        });
+        let stmt = quote_stmt!(self.cx, if $expr { $stmts });
+        self.stmts.push(stmt);
     }
 
     pub fn attribute_end(&mut self) {