Continuing the discussion from How to allow arbitrary expressions in format strings:
So, one of my long-standing desires for rust is to allow generic a interpolation syntax, like "hello {name}"
which doesn't just convert stuff to string using hard-coded Display, but allows the library to interpose custom escaping logic. Basically, I want API like JS or Java template literals, which allow user code to get separate arrays of string fragments and interpolated values. This sounds terribly confusing if you are not already familiar to the idea, but luckily JEP 430: String Templates (Preview) explains this much clearer than I could after staring at the screen for 12 hours
I long believed that that isn't actually possible to express without either variadic generics or dynamic casting, but, after reading that jep, I've realized today that such a generic interpolation API is actually possible in today's rust!
Basically, a macro can expand "{x} + {y}"
into Unnamable(("", " + ", ""), (x, y))
where Unnamable
implements visitor pattern like this one:
pub trait TemplateString<V: TemplateVisitor> {
fn accept(self) -> V::Output;
}
pub trait TemplateVisitor {
type Output;
fn new() -> Self;
fn visit_str(&mut self, s: &'static str);
fn finish(self) -> Self::Output;
}
pub trait TemplateVisit<T> {
fn visit(&mut self, value: &T);
}
So, something like
#[test]
fn shell_escape() {
let branch = "lol; echo pwned!";
let s = interpol!("git switch {branch}" as ShellEscape);
assert_eq!(s, r#"git switch "lol; echo pwned!""#);
}
gets expanded to
struct TS<'a, T0>((&'static str, &'static str), (&'a T0,));
impl<'a, T0, V: my_desire::TemplateVisitor + my_desire::TemplateVisit<T0>>
my_desire::TemplateString<V> for TS<'a, T0>
{
fn accept(self) -> V::Output {
let mut v = V::new();
v.visit_str(self.0 .0);
v.visit(self.1 .0);
v.visit_str(self.0 .1);
v.finish()
}
}
let ts = TS(("git switch ", ""), (&branch,));
my_desire::TemplateString::<ShellEscape>::accept(ts)
}
I've published a quick proof of concept at GitHub - matklad/my-desire / https://crates.io/crates/my-desire I don't intend to push this to production readiness, so feel free to run with the idea!
More examples here: