Frontends
Rust Frontend
Rust Frontend
The Rust frontend is a BraceFrontendBase subclass delivered as a single
1,091-line header. It implements the most distinctive Rust features via a
pipeline of source-level rewrites that run before the brace parser
sees the source:
| Rewrite | Function | Lines |
|---|---|---|
match expressions |
rewriteMatchExpressions |
254-357 |
if let expressions |
rewriteIfLetExpressions |
359-415 |
let-else |
rewriteLetElseExpressions |
426-518 |
if let chains |
rewriteIfLetChains |
584-756 |
RustFrontend::parse (line 12) applies all four in order before
delegating to BraceFrontendBase::parse.
| File | Lines | Purpose |
|---|---|---|
frontends/rust/RustFrontend.h |
1,091 | Frontend class with all method bodies inline (single header) |
frontends/rust/RustStdLib.h |
61 | findStdLibMapping for println!, eprintln!, std::process::exit, std::fs::*, std::env::* |
Language registration
// RustFrontend.h:9-10
std::string languageName() const override { return "rust"; }
std::vector<std::string> fileExtensions() const override { return {".rs"}; }
Only .rs is recognised.
Print patterns
// RustFrontend.h:918-923
return {
{"println!", true, true},
{"print!", true, false}
};
Features (file:line)
Variables: let / let mut / const / static
tryParseVarDecl (line 929) recognises:
let x = 42; // VariableDeclNode(name="x", isConst=false)
let mut x = 42; // VariableDeclNode(name="x", isConst=false, metadata["mut"]="true")
const X: i32 = 42; // VariableDeclNode(name="X", isConst=true, metadata["rust_const"]="true")
static Y: i32 = 42; // VariableDeclNode(name="Y", isConst=false, metadata["rust_static"]="true")
let (a, b) = (1, 2); // Tuple destructuring — emitted as separate VariableDeclNodes
let Point { x, y } = p; // Struct destructuring — fields become separate VariableDeclNodes
Functions: fn / pub fn / async fn / unsafe fn / const fn
tryParseFunctionDeclHeader (line 946) recognises:
fn add(a: i32, b: i32) -> i32 { a + b }
pub fn add(...) -> i32 { ... } // metadata["rust_pub"]="true"
async fn fetch(...) -> ... { ... } // metadata["async"]="true"
unsafe fn danger() { ... } // metadata["unsafe"]="true"
const fn compile_time() { ... } // metadata["const_fn"]="true"
fn new() -> Self { ... } // Associated function (no receiver in name)
fn from(self) -> ... { ... } // Method taking self by value
fn borrow(&self) -> ... { ... } // Method borrowing self
fn mut_take(&mut self) { ... } // Method mutably borrowing self
Receiver detection sets metadata["rust_self_kind"]="value"|"ref"|"ref_mut".
impl [Trait for] Type { … }
tryParseLanguageSpecificStatement (line 1054) recognises impl blocks:
impl Point {
fn new(x: i32, y: i32) -> Point { ... }
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> Result { ... }
}
Both lower to ClassDeclNode(name="<TargetType>", metadata["rust_impl"]="true").
struct Name { … } / struct Name; / struct Name(T);
tryParseLanguageSpecificStatement (line 1029) handles:
| Form | IR |
|---|---|
struct Point { x: i32, y: i32 } |
ClassDeclNode(name="Point", isStruct=true) |
struct Point; |
ClassDeclNode(name="Point", isStruct=true, metadata["rust_unit_struct"]="true") |
struct Point(i32, i32); |
ClassDeclNode(name="Point", isStruct=true, metadata["rust_tuple_struct"]="true") |
enum Name { … }
Line 1005. Standard enum with variants (tuple, struct, unit) all lower to
EnumDeclNode(name=…) with each variant becoming a member.
trait Name { … }
Line 1042. Recognised; lowers to InterfaceDeclNode(name=…). Default
method bodies are parsed but stored as method bodies on the interface.
use module::Item; / use module::*;
Line 987. Lowers to ImportNode(module="module::Item").
mod module_name;
Line 996. Treats the module as an ImportNode(module="module_name"). The
actual module body is loaded separately by the CLI driver.
macro_rules! name { … }
Line 1072. Detected; lowers to FunctionDeclNode(name="…", metadata["macro_rules"]="true").
Match expressions
rewriteMatchExpressions (line 254-357) translates match expr { pat => body, … } to switch (expr) { case pat: body; … default: ; } so the
brace parser handles it.
match n {
0 => "zero",
1 | 2 => "small",
_ => "big",
}
Lowers to:
switch (n) {
case 0: "zero";
case 1: "small";
case 2: "small";
default: "big";
}
Or-patterns (1 | 2) are expanded into multiple case entries. Wildcard
_ becomes default.
If let chains
rewriteIfLetChains (line 584-756) handles if let … = … && let … = … and
the alternative pattern else if let:
if let Some(x) = a && let Some(y) = b && x != y {
do(x, y)
} else {
fallback()
}
Lowers to nested if statements:
{
let __tmp_a = a;
if (__tmp_a is_some) {
let x = __tmp_a.unwrap();
let __tmp_b = b;
if (__tmp_b is_some) {
let y = __tmp_b.unwrap();
if (x != y) {
do(x, y)
} else {
fallback()
}
} else {
fallback()
}
} else {
fallback()
}
}
If let expressions
rewriteIfLetExpressions (line 359-415) handles the expression form
if let Pat = expr { … } else { … } (used as an expression value):
let result = if let Some(x) = opt { x + 1 } else { 0 };
Lowers to:
{
let __result;
if (let_rewritten) {
__result = x + 1;
} else {
__result = 0;
}
__result
}
Let-else
rewriteLetElseExpressions (line 426-518) handles let Pat = expr else { diverge; };:
let Some(x) = opt else {
return;
};
Lowers to:
let Some(x) = opt;
The diverge body is dropped (with a comment at line 421 noting the limitation for destructuring patterns). See limitations.md.
async fn / .await
async fn sets metadata["async"]="true". .await expressions are
parsed by the brace parser's expression parser, emitting AwaitNode.
gen blocks / yield
yield and gen blocks (Rust nightly, MCP 2024) are not yet supported
beyond recognising yield expr; as a YieldNode (inherited from
BraceFrontendBase).
Lifetimes
Lifetimes (<'a>, &'a T, struct Foo<'a>, …) are passed through to
the C++ backend. The frontend doesn't currently enforce lifetime
correctness (Rust's borrow checker is not implemented). See
limitations.md.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
The lifetime annotations are stored in metadata["rust_lifetimes"]="a" on
the function declaration, but the compiler doesn't validate them.
Ownership and borrowing
Ownership (move) and borrowing (&, &mut) are type-erased at
the IR level. The frontend doesn't enforce ownership rules — it's
treated as a syntactic transformation only.
Closures
Closures (|x| x + 1, move |x| x + 1, || { … }) are parsed by
BraceFrontendBase's expression parser. move closures set
metadata["rust_move_closure"]="true".
Async / await
async fn (line 949) sets metadata["async"]="true". await is parsed
as an expression by BraceFrontendBase. async {} block expressions and
async move {} are recognised.
Standard library mapping (RustStdLib.h)
The complete table at frontends/rust/RustStdLib.h:38-50:
| Rust | MPL module.function |
|---|---|
println!(...) |
Console.WriteLine(...) |
print!(...) |
Console.Write(...) |
eprintln!(...) |
Console.ErrorWriteLine(...) |
eprint!(...) |
Console.ErrorWrite(...) |
std::process::exit(code) |
Process.Exit(code) |
std::env::var(name) |
Environment.GetVariable(name) |
std::env::current_dir() |
Environment.GetCurrentDirectory() |
std::fs::read_to_string(path) |
IO.ReadFileText(path) |
std::fs::write(path, contents) |
IO.WriteFileText(path, contents) |
IR examples
Example 1 — match expression
fn describe(n: i32) -> &'static str {
match n {
0 => "zero",
1 | 2 => "small",
_ => "big",
}
}
After rewriteMatchExpressions, the source becomes:
fn describe(n: i32) -> &'static str {
switch (n) {
case 0: "zero";
case 1: "small";
case 2: "small";
default: "big";
}
}
Lowers to:
FunctionDeclNode(name="describe", parameters=[("auto","n")], returnType="&'static str")
body:
SwitchNode(expr=IdentifierNode("n"))
cases:
case 0: body = "zero"
case 1: body = "small"
case 2: body = "small"
default: body = "big"
Example 2 — if let chain
fn process(opt_a: Option<i32>, opt_b: Option<i32>) -> i32 {
if let Some(x) = opt_a && let Some(y) = opt_b && x != y {
x + y
} else {
-1
}
}
After rewriteIfLetChains, the body is a sequence of nested ifs with
binding extraction.
Example 3 — let-else
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
let Some(i) = s.find(' ') else {
return &s[..];
};
&s[..i]
}
let Some(i) = s.find(' ') else { return &s[..]; }; becomes:
let Some(i) = s.find(' ');
with the else { return &s[..]; } branch dropped (limitation).
What is not supported (Rust-specific gaps)
- Borrow checker: lifetimes are not validated. The compiler
will accept
&'a Treturning a different reference thanTwith no complaint. - Trait object dispatch:
dyn Traitsyntax passes through but isn't lowered to a virtual table. - Generic type parameters:
<T: Bound>are stripped to the type identifier. - Destructuring patterns in
let-else: diverge body is dropped. - Async traits (
async fnin traits): not modelled. - Gen blocks (
gen {}): not yet supported beyondyield expr. impl Traitreturn types: stripped.?operator: not yet lowered to a Try-step call.- Const generics:
<const N: usize>: stripped. - Where clauses:
where T: Boundare dropped. - Visibility modifiers other than
pub(pub(crate),pub(super),pub(in path)): dropped.