Template literals
Backtick strings with ${} interpolation, what a slot accepts, and why interpolating a variable does not move it.
A template literal is delimited by backticks and evaluates to an owned String. ${ } drops an
expression into the surrounding text.
import String from "std::string";
function banner(name: str, version: i32): String {
return `hello ${name} v${version}`;
}
Without a slot it is still a String, not a str.
let plain: String = `no interpolation`;
A literal may span several lines, and the newlines are part of the value.
let block: String = `first line
second line`;
Slots
A slot takes any expression, another template included.
function describe(version: i32): String {
let computed: String = `sum=${version + 1}`;
let nested: String = `outer ${`inner ${version}`}`;
return computed.concat(nested);
}
Braces, strings and blocks written inside a slot are scanned as ordinary code, so a } closes the
slot only when it matches the ${ that opened it.
Escapes
The string escapes all apply, plus \` for a literal backtick and \${ for text that must not
open a slot.
let raw: String = `a \` b \${notASlot}`; // a ` b ${notASlot}
What a slot accepts
Interpolation resolves through String::concat, so a slot holds a String, a str, a char, a
boolean, or any integer or float.
function render(label: str, count: i32, ready: boolean, initial: char): String {
return `${label}: ${count} ready=${ready} initial=${initial}`;
}
A record needs an explicit conversion. The compiler does not infer a method’s type parameters from its arguments yet, so a generic conversion cannot be selected from the slot itself.
import String from "std::string";
record Point {
x: i32;
y: i32;
public toString(&self): String {
return `(${self.x}, ${self.y})`;
}
}
function show(point: &Point): String {
return `point=${point.toString()}`;
}
A slot whose type has no concat overload reports A0100 and lists the signatures that do exist.
Ownership
A slot that names a place — a variable, a field, an element — is borrowed, not moved, so the value stays usable afterwards.
import String from "std::string";
function twice(): String {
let owned: String = String::create("kept");
let first: String = `a=${owned}`;
let second: String = `b=${owned}`;
return first.concat(second);
}
A temporary is passed by value, which is what you want: there is nothing left to keep alive.
let text: String = `now=${String::create("temporary")}`;
A slot that already holds a reference is not borrowed a second time, so a &String parameter
interpolates directly.
function label(name: &String): String {
return `[${name}]`;
}
How it compiles
The literal is desugared into a String::create call followed by one concat per part.
let direct: String = `a=${1} b`;
// builds the same value as
// String::create("a=").concat(1).concat(" b")
Empty chunks are dropped, so `${a}${b}` performs two concatenations and not four. Diagnostics
point at the slot you wrote, not at the generated call.
The formatter prints a template verbatim: whitespace inside ${ } is left exactly as written.