Reputation: 21315
For example, imagine a theoretical lets!
macro that would be written like this:
if lets![Some(x) = x && Some(y) = y] {
...
}
which would expand to something like:
if let Some(x) = x {
if let Some(y) = y {
...
}
}
I am aware that if-let-chains are coming soon to Rust, so the question isn't specifically about let
chains here. Rather, it's more around providing macros which ultimately aren't hygienic, and so the resulting code should be parsed only after expansion of the macro -- e.g. imagine a macro filling in part of an if
conditional, and affecting how code following the expansion should be parsed.
Is such a thing possible with Rust?
Upvotes: 3
Views: 348
Reputation: 8980
While theoretically possible, you managed to choose one of the harder edge cases with macros. The primary reason is that this macro is unable to expand to the desired equivalent code without modifying areas outside the macro's bounds. Lets do this with a slightly modified example version of your example to show how this is problematic.
if lets![Some(x) = a && Some(y) = b] {
// etc
}
It may seem rather obvious, but it is not possible to expand to two nested if statements because that would require additional curly braces outside the macro's extents.
if let Some(x) = a {
if let Some(y) = b {
// etc
} // <- How do we create this brace?
}
The easy solution though would be to merge both matches into a single match. However we will lose the short-circuit properties by evaluating both expressions ahead of time. Unfortunately this is also not possible since if let
is considered a special expression and emitting let x = y
would not be a valid expression on its own. This could change in the future, but I doubt it as it would necessitate that variable declaration be allowed in any arbitrary expression.
// Theoretical macro expansion
if let (Some(x), Some(y)) = (a, b) {
// etc
}
Now I have thinking here for a while but I can not think of a way to remedy the fact that if let
is special operator and we need to emit some tokens that go between if
and { /* etc */ }
which can function as an if let
. Even procedural macros which have full access to emit whatever tokens they want likely would not be able to do this in the way you describe. Conveniently, the most recent question (Rust Procedural Macro - macro expansion ignores token `,`) asked under the [rust] tag gives some insight into this. Procedural macros can not just emit any arbitrary tokens and expect to be placed into the assembled code. They must be valid within the contexts that they are used. For example, we would not want macros adding un-matched parentheses or multiple comma separated values like in the linked question. The macro needs to cover the entirety of the if statement to expand properly.
In fact, this is likely the reason the popular if_chain
crate works the way it does. If you want this functionality and do not want to add nightly features or spend too much time figuring out how to make it work, then this crate is probably the way to go. Here is a quick example from their documentation on how their macro is used.
if_chain! {
if let Some(y) = x;
if y.len() == 2;
if let Some(z) = y;
then {
do_stuff_with(z);
}
}
Upvotes: 3