diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index b012cf4..d194135 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 713eafc..8371927 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -23,9 +23,6 @@ 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))
 }
@@ -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(..)))
 }
@@ -172,19 +172,11 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 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());
@@ -308,9 +300,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
         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 8c52cae..0b2bdf5 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 265cc00..be02f40 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;
 }
 
@@ -225,7 +225,7 @@ mod control {
             let mut s = String::new();
             html!(s, {
                 #if let Some(value) = input {
-                    $value
+                    ^value
                 } #else {
                     "oh noes"
                 }
@@ -240,7 +240,7 @@ mod control {
         let mut s = String::new();
         html!(s, {
             ul #for pony in &ponies {
-                li $pony
+                li ^pony
             }
         }).unwrap();
         assert_eq!(s, concat!(
@@ -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,6 +327,55 @@ fn splice_with_path() {
     }
 
     let mut s = String::new();
-    html!(s, $inner::name()).unwrap();
+    html!(s, ^inner::name()).unwrap();
     assert_eq!(s, "Maud");
 }
+
+#[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 ");
+}