diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index b012cf42..d194135b 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -23,6 +23,17 @@ impl<T: fmt::Display + ?Sized> Render for T {
     }
 }
 
+/// Represents a type that can be rendered as HTML just once.
+pub trait RenderOnce {
+    fn render_once(self, &mut fmt::Write) -> fmt::Result;
+}
+
+impl<'a, T: Render + ?Sized> RenderOnce for &'a T {
+    fn render_once(self, w: &mut fmt::Write) -> fmt::Result {
+      Render::render(self, w)
+    }
+}
+
 /// A wrapper that renders the inner value without escaping.
 #[derive(Debug)]
 pub struct PreEscaped<T>(pub T);
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index c394c017..0735bc90 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -23,11 +23,8 @@ macro_rules! parse_error {
     ($self_:expr, $sp:expr, $msg:expr) => (error!($self_.render.cx, $sp, $msg))
 }
 
-macro_rules! dollar {
-    () => (TokenTree::Token(_, Token::Dollar))
-}
-macro_rules! pound {
-    () => (TokenTree::Token(_, Token::Pound))
+macro_rules! at {
+    () => (TokenTree::Token(_, Token::At))
 }
 macro_rules! dot {
     () => (TokenTree::Token(_, Token::Dot))
@@ -53,6 +50,9 @@ macro_rules! minus {
 macro_rules! slash {
     () => (TokenTree::Token(_, Token::BinOp(BinOpToken::Slash)))
 }
+macro_rules! caret {
+    () => (TokenTree::Token(_, Token::BinOp(BinOpToken::Caret)))
+}
 macro_rules! literal {
     () => (TokenTree::Token(_, Token::Literal(..)))
 }
@@ -156,35 +156,27 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 try!(self.literal(tt, false))
             },
             // If
-            [pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
+            [at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
                 self.shift(2);
                 try!(self.if_expr(sp));
             },
             // For
-            [pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => {
+            [at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => {
                 self.shift(2);
                 try!(self.for_expr(sp));
             },
             // Call
-            [pound!(), ident!(sp, name), ..] if name.name.as_str() == "call" => {
+            [at!(), ident!(sp, name), ..] if name.name.as_str() == "call" => {
                 self.shift(2);
                 let func = try!(self.splice(sp));
                 self.render.emit_call(func);
             },
             // Splice
-            [ref tt @ dollar!(), ..] => {
+            [ref tt @ caret!(), ..] => {
                 self.shift(1);
                 let expr = try!(self.splice(tt.get_span()));
                 self.render.splice(expr);
             },
-            [substnt!(sp, ident), ..] => {
-                self.shift(1);
-                // Parse `SubstNt` as `[Dollar, Ident]`
-                // See <https://github.com/lfairy/maud/issues/23>
-                let prefix = TokenTree::Token(sp, Token::Ident(ident, IdentStyle::Plain));
-                let expr = try!(self.splice_with_prefix(prefix));
-                self.render.splice(expr);
-            },
             // Element
             [ident!(sp, _), ..] => {
                 let name = try!(self.name());
@@ -222,9 +214,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
         Ok(())
     }
 
-    /// Parses and renders an `#if` expression.
+    /// Parses and renders an `@if` expression.
     ///
-    /// The leading `#if` should already be consumed.
+    /// The leading `@if` should already be consumed.
     fn if_expr(&mut self, sp: Span) -> PResult<()> {
         // Parse the initial if
         let mut if_cond = vec![];
@@ -239,11 +231,11 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(1);
                 if_cond.push(tt.clone());
             },
-            [] => parse_error!(self, sp, "expected body for this #if"),
+            [] => parse_error!(self, sp, "expected body for this @if"),
         }}
-        // Parse the (optional) else
+        // Parse the (optional) @else
         let else_body = match self.input {
-            [pound!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => {
+            [at!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => {
                 self.shift(2);
                 match self.input {
                     [keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
@@ -263,7 +255,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                         self.shift(1);
                         Some(try!(self.block(sp, &d.tts)))
                     },
-                    _ => parse_error!(self, sp, "expected body for this #else"),
+                    _ => parse_error!(self, sp, "expected body for this @else"),
                 }
             },
             _ => None,
@@ -272,9 +264,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
         Ok(())
     }
 
-    /// Parses and renders a `#for` expression.
+    /// Parses and renders a `@for` expression.
     ///
-    /// The leading `#for` should already be consumed.
+    /// The leading `@for` should already be consumed.
     fn for_expr(&mut self, sp: Span) -> PResult<()> {
         let mut pattern = vec![];
         loop { match self.input {
@@ -286,7 +278,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(1);
                 pattern.push(tt.clone());
             },
-            _ => parse_error!(self, sp, "invalid #for"),
+            _ => parse_error!(self, sp, "invalid @for"),
         }}
         let pattern = try!(self.with_rust_parser(pattern, RustParser::parse_pat));
         let mut iterable = vec![];
@@ -301,16 +293,16 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(1);
                 iterable.push(tt.clone());
             },
-            _ => parse_error!(self, sp, "invalid #for"),
+            _ => parse_error!(self, sp, "invalid @for"),
         }}
         let iterable = try!(self.with_rust_parser(iterable, RustParser::parse_expr));
         self.render.emit_for(pattern, iterable, body);
         Ok(())
     }
 
-    /// Parses and renders a `$splice`.
+    /// Parses and renders a `^splice`.
     ///
-    /// The leading `$` should already be consumed.
+    /// The leading `^` should already be consumed.
     fn splice(&mut self, sp: Span) -> PResult<P<Expr>> {
         // First, munch a single token tree
         let prefix = match self.input {
@@ -323,24 +315,24 @@ impl<'cx, 'i> Parser<'cx, 'i> {
         self.splice_with_prefix(prefix)
     }
 
-    /// Parses and renders a `$splice`, given a prefix that we've already
+    /// Parses and renders a `^splice`, given a prefix that we've already
     /// consumed.
     fn splice_with_prefix(&mut self, prefix: TokenTree) -> PResult<P<Expr>> {
         let mut tts = vec![prefix];
         loop { match self.input {
-            // Munch attribute lookups e.g. `$person.address.street`
+            // Munch attribute lookups e.g. `^person.address.street`
             [ref dot @ dot!(), ref ident @ ident!(_, _), ..] => {
                 self.shift(2);
                 tts.push(dot.clone());
                 tts.push(ident.clone());
             },
-            // Munch tuple attribute lookups e.g. `$person.1.2`
+            // Munch tuple attribute lookups e.g. `^person.1.2`
             [ref dot @ dot!(), ref num @ integer!(), ..] => {
                 self.shift(2);
                 tts.push(dot.clone());
                 tts.push(num.clone());
             },
-            // Munch path lookups e.g. `$some_mod::Struct`
+            // Munch path lookups e.g. `^some_mod::Struct`
             [ref sep @ modsep!(), ref ident @ ident!(_, _), ..] => {
                 self.shift(2);
                 tts.push(sep.clone());
diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs
index 8c52caea..0b2bdf57 100644
--- a/maud_macros/src/render.rs
+++ b/maud_macros/src/render.rs
@@ -118,7 +118,7 @@ impl<'cx> Renderer<'cx> {
     /// Appends the result of an expression, with the specified escaping method.
     pub fn splice(&mut self, expr: P<Expr>) {
         let w = self.writer;
-        let expr = quote_expr!(self.cx, ::maud::Render::render(&$expr, &mut *$w));
+        let expr = quote_expr!(self.cx, { use ::maud::RenderOnce; $expr.render_once(&mut *$w) });
         let stmt = self.wrap_try(expr);
         self.push(stmt);
     }
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 8879bccb..83d7888a 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -93,7 +93,7 @@ mod splices {
     #[test]
     fn literals() {
         let mut s = String::new();
-        html!(s, $"<pinkie>").unwrap();
+        html!(s, ^"<pinkie>").unwrap();
         assert_eq!(s, "&lt;pinkie&gt;");
     }
 
@@ -101,7 +101,7 @@ mod splices {
     fn raw_literals() {
         use maud::PreEscaped;
         let mut s = String::new();
-        html!(s, $PreEscaped("<pinkie>")).unwrap();
+        html!(s, ^PreEscaped("<pinkie>")).unwrap();
         assert_eq!(s, "<pinkie>");
     }
 
@@ -109,7 +109,7 @@ mod splices {
     fn blocks() {
         let mut s = String::new();
         html!(s, {
-            ${
+            ^{
                 let mut result = 1i32;
                 for i in 2..11 {
                     result *= i;
@@ -142,7 +142,7 @@ mod splices {
     #[test]
     fn statics() {
         let mut s = String::new();
-        html!(s, $BEST_PONY).unwrap();
+        html!(s, ^BEST_PONY).unwrap();
         assert_eq!(s, "Pinkie Pie");
     }
 
@@ -150,7 +150,7 @@ mod splices {
     fn closures() {
         let best_pony = "Pinkie Pie";
         let mut s = String::new();
-        html!(s, $best_pony).unwrap();
+        html!(s, ^best_pony).unwrap();
         assert_eq!(s, "Pinkie Pie");
     }
 
@@ -177,7 +177,7 @@ mod splices {
         };
         let mut s = String::new();
         html!(s, {
-            "Name: " $pinkie.name ". Rating: " $pinkie.repugnance()
+            "Name: " ^pinkie.name ". Rating: " ^pinkie.repugnance()
         }).unwrap();
         assert_eq!(s, "Name: Pinkie Pie. Rating: 1");
     }
@@ -186,7 +186,7 @@ mod splices {
     fn nested_macro_invocation() {
         let best_pony = "Pinkie Pie";
         let mut s = String::new();
-        html!(s, $(format!("{}", best_pony))).unwrap();
+        html!(s, ^(format!("{}", best_pony))).unwrap();
         assert_eq!(s, "Pinkie Pie");
     }
 }
@@ -195,7 +195,7 @@ mod splices {
 fn issue_13() {
     let owned = String::from("yay");
     let mut s = String::new();
-    html!(s, $owned).unwrap();
+    html!(s, ^owned).unwrap();
     let _ = owned;
 }
 
@@ -205,13 +205,13 @@ mod control {
         for (number, &name) in (1..4).zip(["one", "two", "three"].iter()) {
             let mut s = String::new();
             html!(s, {
-                #if number == 1 {
+                @if number == 1 {
                     "one"
-                } #else if number == 2 {
+                } @else if number == 2 {
                     "two"
-                } #else if number == 3 {
+                } @else if number == 3 {
                     "three"
-                } #else {
+                } @else {
                     "oh noes"
                 }
             }).unwrap();
@@ -224,9 +224,9 @@ mod control {
         for &(input, output) in [(Some("yay"), "yay"), (None, "oh noes")].iter() {
             let mut s = String::new();
             html!(s, {
-                #if let Some(value) = input {
-                    $value
-                } #else {
+                @if let Some(value) = input {
+                    ^value
+                } @else {
                     "oh noes"
                 }
             }).unwrap();
@@ -239,8 +239,8 @@ mod control {
         let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"];
         let mut s = String::new();
         html!(s, {
-            ul #for pony in &ponies {
-                li $pony
+            ul @for pony in &ponies {
+                li ^pony
             }
         }).unwrap();
         assert_eq!(s, concat!(
@@ -288,9 +288,9 @@ fn call() {
             panic!("oh noes")
         };
     html!(s, {
-        #call ducks
-        #call (|w: &mut fmt::Write| write!(w, "Geese"))
-        #call swans(true)
+        @call ducks
+        @call (|w: &mut fmt::Write| write!(w, "Geese"))
+        @call swans(true)
     }).unwrap();
     assert_eq!(s, "DucksGeeseSwans");
 }
@@ -306,7 +306,7 @@ fn issue_23() {
     }
 
     let name = "Lyra";
-    let s = to_string!(p { "Hi, " $name "!" });
+    let s = to_string!(p { "Hi, " ^name "!" });
     assert_eq!(s, "<p>Hi, Lyra!</p>");
 }
 
@@ -314,7 +314,7 @@ fn issue_23() {
 fn tuple_accessors() {
     let mut s = String::new();
     let a = ("ducks", "geese");
-    html!(s, { $a.0 }).unwrap();
+    html!(s, { ^a.0 }).unwrap();
     assert_eq!(s, "ducks");
 }
 
@@ -327,7 +327,7 @@ fn splice_with_path() {
     }
 
     let mut s = String::new();
-    html!(s, $inner::name()).unwrap();
+    html!(s, ^inner::name()).unwrap();
     assert_eq!(s, "Maud");
 }
 
@@ -365,3 +365,124 @@ fn classes_shorthand_with_attrs() {
     html!(s, p { "Hi, " span.name.here id="thing" { "Lyra" } "!" }).unwrap();
     assert_eq!(s, "<p>Hi, <span class=\"name here\" id=\"thing\">Lyra</span>!</p>");
 }
+
+#[test]
+fn multirender() {
+    struct R<'a>(&'a str);
+    impl<'a> maud::Render for R<'a> {
+        fn render(&self, w: &mut std::fmt::Write) -> std::fmt::Result {
+            w.write_str(self.0)
+        }
+    }
+
+    let mut s = String::new();
+    let r = R("pinkie ");
+    html!(s, ^r).unwrap();
+    html!(s, ^r).unwrap();
+    // R is not-Copyable so this shows that it will auto-ref splice arguments that implement Render.
+    assert_eq!(s, "pinkie pinkie ");
+}
+
+#[test]
+fn render_once_by_move() {
+    struct Once<'a>(&'a str);
+    impl<'a> maud::RenderOnce for Once<'a> {
+        fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result {
+            w.write_str(self.0)
+        }
+    }
+
+    let mut s = String::new();
+    let once = Once("pinkie");
+    html!(s, ^once).unwrap();
+    assert_eq!(s, "pinkie");
+}
+
+#[test]
+fn render_once_by_move_with_copy() {
+    #[derive(Clone, Copy)]
+    struct Once<'a>(&'a str);
+    impl<'a> maud::RenderOnce for Once<'a> {
+        fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result {
+            w.write_str(self.0)
+        }
+    }
+
+    let mut s = String::new();
+    let once = Once("pinkie ");
+    html!(s, ^once).unwrap();
+    html!(s, ^once).unwrap();
+    assert_eq!(s, "pinkie pinkie ");
+}
+
+#[test]
+fn issue_26() {
+    macro_rules! to_string {
+        ($($x:tt)*) => {{
+            let mut s = String::new();
+            html!(s, $($x)*).unwrap();
+            s
+        }}
+    }
+
+    let name = "Lyra";
+    let s = to_string!(p { "Hi, " ^(name) "!" });
+    assert_eq!(s, "<p>Hi, Lyra!</p>");
+}
+
+#[test]
+fn issue_26_2() {
+    macro_rules! to_string {
+        ($($x:tt)*) => {{
+            let mut s = String::new();
+            html!(s, $($x)*).unwrap();
+            s
+        }}
+    }
+
+    let name = "Lyra";
+    let s = to_string!(p { "Hi, " ^("person called ".to_string() + name) "!" });
+    assert_eq!(s, "<p>Hi, person called Lyra!</p>");
+}
+
+#[test]
+fn issue_26_3() {
+    macro_rules! to_string {
+        ($($x:tt)*) => {{
+            let mut s = String::new();
+            html!(s, $($x)*).unwrap();
+            s
+        }}
+    }
+
+    let name = "Lyra";
+    let s = to_string!(p { "Hi, " ^{"person called ".to_string() + name} "!" });
+    assert_eq!(s, "<p>Hi, person called Lyra!</p>");
+}
+
+#[test]
+fn issue_21() {
+    macro_rules! greet {
+        () => ({
+            let mut result = String::new();
+            let name = "Pinkie Pie";
+            html!(result, p { "Hello, " ^name "!" }).map(|()| result)
+        })
+    }
+
+    let s = greet!().unwrap();
+    assert_eq!(s, "<p>Hello, Pinkie Pie!</p>");
+}
+
+#[test]
+fn issue_21_2() {
+    macro_rules! greet {
+        ($name:expr) => ({
+            let mut result = String::new();
+            html!(result, p { "Hello, " ^$name "!" }).map(|()| result)
+        })
+    }
+
+    let s = greet!("Pinkie Pie").unwrap();
+    assert_eq!(s, "<p>Hello, Pinkie Pie!</p>");
+}