Swift's Guard Syntax in Rust
TLDR
The following Rust macro lets you write Swift-style guard statements in Rust:macro_rules! guard {
(@parse ($($cond:tt)*) else $block:block) => {
let true = ($($cond)*) else $block;
};
(@parse ($($cond:tt)*) $next:tt $($rest:tt)*) => {
guard!(@parse ($($cond)* $next) $($rest)*);
};
($($tt:tt)+) => {
guard!(@parse () $($tt)+);
};
} For example, this in Swift:
guard data == 0 else {
print("data is not 0 :(")
return
}
print("data is 0") can be written like this in Rust by using the above macro:
guard!(data == 0 else {
println!("data is not 0 :(");
return
});
println!("data is 0");Today my girlfriend told me about Swift’s guard syntax. Here’s an example snippet:
guard data == 0 else {
print("data is not 0 :(")
return
}
print("data is 0") For those unfamiliar, it’s semantically similar to an if !condition {...} but the compiler ensures that the block diverges (returns, breaks, throws an exception, etc). For example, deleting the return within the else block in the above snippet would make the Swift compiler would raise a compiler error.
A guard assures the reader that a condition is guaranteed to be met once past the guard statement while still allowing for arbitrary handling within the else block in the case the condition is not met, as long as that handling eventually diverges. So, swift guard blocks are essentially an assert with customizable logic for what to do if/when the assert fails.
Rust’s Semi-Equivalent let-else
Rust has a similar construct, the let-else statement. This is often used for destructuring a tagged enum type such as a Result or an Option for later use while still having custom logic in case the value wasn’t the desired enum variant. For example:
let Some(value) = maybe_got_value() else {
println!("didn't get a value :(");
return Err("didn't get value");
}
println!("got the value! {}", value);
While Swift’s guard is frequently used to perform boolean assertions, let-else is practically never used for boolean assertions. This got me wondering whether I could even use let-else for an assertion, which led to the cursed syntax below:
let 0 = data else {
println!("data is not 0 :(");
return
};
println!("data is 0"); Believe it or not, this code compiles, even though it looks like I’m setting 0 to equal data. But seen another way, it’s assigning 0 to equal data only if data already equals 0 (which would obviously be a no-op).
What the above code is doing is even clearer when we desugar the let-else syntax to the more intuitive match syntax:
fn main() {
match data {
0 => {
println!("data is 0");
}
_ => {
println!("data is not 0 :(")
}
}
While the initial let 0 = data syntax successfully replicated the first Swift example, it does not allow for arbitrary boolean expressions. To accomplish this, we must slightly augment the above technique:
let true = data == 0 else {
println!("data is not 0 :(");
return
};
println!("data is 0"); Now data == 0 can be substituted with any boolean expression, making the let-else syntax truly equivalent to swift’s boolean guard syntax.
Improving the Syntax with a Macro
I don’t particularly like the clunky let true = condition syntax, so I wrote out my ideal syntax using a rust macro:
guard!(data == 0 else {
println!("data is not 0 :(");
return
});
println!("data is 0"); Then I asked an LLM to come up with the macro definition that would convert the above syntax into the let true = condition syntax. It came up with this:
macro_rules! guard {
(@parse ($($cond:tt)*) else $block:block) => {
let true = ($($cond)*) else $block;
};
(@parse ($($cond:tt)*) $next:tt $($rest:tt)*) => {
guard!(@parse ($($cond)* $next) $($rest)*);
};
($($tt:tt)+) => {
guard!(@parse () $($tt)+);
};
} If you’re curious about how the above macro works, I highly recommend reading through “The Little Book of Rust Macros”, specifically the section on “incremental TT munchers”.
Now that I have a syntax which pleases me, I look forward to using it in my own crates.
Prior Work
I’m not the first person to draw the connection between Swift’s guard and Rust’s let true = ..., that credit goes to m-ronchi on the Rust Internals forum. I’m also not the first to consider encapsulating such syntax into a macro. Further credit goes to richr who defined a guard_else macro farther down the forum chain that is used like so:
for i in 1..=100 {
guard_else!(i < 10, {
// Arbitrary logic here...
println!("DONE");
break; // Compiler complains if this is missing (which is good!)
});
// ...
} While I do concede that richr’s macro definition code is significantly easier to read, I think added complexity in the definition is worth clearer syntax when using the macro.
Please Feel free to copy and paste my macro!
For a second I considered publishing the above guard! macro to crates.io, but I didn’t think it was worth going through all the work of publishing a crate just for 10 lines of LLM-generated code. So, if you want to use it, please feel free to copy and paste it into your own rust crates. Formally, I license the snippet under CC0-1.0, although honestly I think it’s already in the public domain, at least in the US, because it was fully generated by an LLM.
For my own projects I’ll probably just paste the macro into my crates whenever I need it, and anyone else is more than welcome to do the same. If anyone reading this would like to go through the effort of publishing this to crates.io, you’re more than welcome to do so, just be sure to keep the license CC0-1.0 when doing so.
Will I actually use the macro?
Maybe, but honestly I doubt it. I rarely write code where later code depends on a value being a specific value because a lot of my Rust code encodes different options for parameters into tagged enums where the let ExampleEnum::Case = instance else {...} syntax works fine for me. Regardless, it was a fun exercise in rust macros, unearthing cursed rust syntax, and a good inspiration for me to finally setup my developer blog.