I'm trying to write a macro that automatically expands a set of enum variants into a builder (base on a previous question, though I don't think that's relevant). Basically, I want to pass it a value, a builder struct and a list of enum variants and generate match arms. The result should be equivalent to this:
match a {
Attribute::InsuranceGroup { value } => builder.insurance_group(value),
};
I think I've managed to get fairly close, but I can't figure out how to convert the UpperCamelCase InsuranceGroup to lower_camel_case insurance_group using the casey crate, in order to call the builder method. Currently, I have:
macro_rules! unwrap_value {
($var:ident, $builder:ident, [ $( $enum:ident :: $variant:ident ),+ $(,)? ]) => {
match $var {
$($enum::$variant { value } => $builder.snake!($variant) (value),)*
}
}
}
unwrap_value! {
a, builder, [Attribute::InsuranceGroup]
}
However, this fails at $builder.snake!($variant) with the analyser complaining that it expects (, ,, ., ::, ?, } or an operator, instead of !.
I also tried moving snake! outside, snake!($builder.$variant), but then it says it can't find $builder in this scope.
While I would be interested in any suggestions of alternatives to using the builder which would eliminate this problem, I'm more interested in understanding what I'm doing wrong with the macros in order to better understand them.
While I would strongly recomment using the paste crate (23,999,580 total downloads vs 7,611 for casey as the time of writing, paste is even available on the playground!), I will explain here why casey didn't work for the sake of knowledge (and suggest a solution! But one you shouldn't use).
The first version didn't work because macros cannot be used after the dot. You can check that out easily:
macro_rules! m {
() => { m };
}
v.m!();
error: expected one of `(`, `.`, `::`, `;`, `?`, `}`, or an operator, found `!`
--> src/main.rs:6:4
|
6 | v.m!();
| ^ expected one of 7 possible tokens
Neither they will be allowed here in the future (probably), because this will collide (and confuse) with the possibility for postfix macros in the future. This is stated by the reference, too: no MacroInvocation is allowed after the dot in MethodCallExpr, TupleIndexingExpr or FieldExpr.
The natural solution is to wrap the whole call in macros (insert the (value) also there, because otherwise it considers that as if you've written (v.method)(value) and that is invalid method call): snake!($builder.$variant(value)).
However, now the compiler complains with a strange error:
error[E0425]: cannot find value `builder` in this scope
--> src\lib.rs:6:44
|
6 | $($enum::$variant { value } => snake!($builder.$variant(value)),)*
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: a unit struct with a similar name exists: `Builder`
...
17 | struct Builder;
| --------------- similarly named unit struct `Builder` defined here
...
26 | / unwrap_value! {
27 | | a, builder, [Attribute::InsuranceGroup]
28 | | }
| |_____- in this macro invocation
|
= note: this error originates in the macro `snake` (in Nightly builds, run with -Z macro-backtrace for more info)
What? WHAT?? Surely builder is in scope! We declared it! Just straight before the macro call (me at least)!
The result is macro hygiene.
How does snake!() generates its identifiers? Well, let's dive into the code...
Ident::new(&transform(ident.to_string()), Span::call_site())
Hmm, let's go to the docs of Span::call_site()...
The span of the invocation of the current procedural macro. Identifiers created with this span will be resolved as if they were written directly at the macro call location (call-site hygiene) and other code at the macro call site will be able to refer to them as well.
(Emphasis mine. "The macro" here refers to the calling proc-macro, i.e. snake!() in this case).
And indeed, we can reproduce this error if we just spell out builder ourselves. Because the macro doesn't see builder. It can only see variables it created. In fact, using call-site hygiene here is a bug of casey. paste does the correct thing and uses the hygiene of the original identifier.
In fact, this just reveals a bigger problem with our approach: what if the variable will not be snake-cased already? It should be, but if not, we will snake-case it incorrectly!
But what can we do? The paste crate gives us proper control over what identifiers we want to change, but casey does not (another reason to choose paste)! Can we walk against our proc macro?
Can we??
Maybe. I see at least one approach that doesn't require us to do that (find it yourself!). But I actually do want to work against our proc-macro. Try at least. Because why not? It's funny.
snake!() doesn't look into groups. That means, it will change something like AbC - but not something like (AbC). So we can just wrap $builder in parentheses...
$($enum::$variant { value } => snake!(($builder).$variant(value)),)*
(No need to handle value because it is already snake-cased, and originates in our macro).
And it works! Voilà! (You should not depend on that. This may be a bug and subject to changes.)
Well after hunting for hours I finally found a solution shortly after posting. Instead of using the casey crate I'm using the paste crate, which includes the functionality I need. The macro code then becomes:
macro_rules! unwrap_value {
($var:ident, $builder:ident, [ $( $enum:ident :: $variant:ident ),+ $(,)? ]) => {
match $var {
$($enum::$variant { value } => paste!($builder.[<$variant:snake>](value)) ,)*
}
}
}
Related
let a = [10, 20, 30, 40, 50];
let mut index_ = 0;
while index_ < 5 {
println!("{}", a[index_]); // works
println!("{a[index_]}"); // does not work
println!("{index_}"); // works
println!("{}", index_); // works
index_ = index_ + 1;
}
Why does "{a[index_]}" not work? It seems like it should to me.
The documentation says that this syntax is called "named parameters", and it supports names, not arbitrary expressions.
If a named parameter does not appear in the argument list, format! will reference a variable with that name in the current scope.
a[index_] is not a valid name (but is a valid expression), so you get the error because the format! syntax doesn't let you use arbitrary expressions inside {}, like in Python. Note that println! "uses the same syntax as format!", so the same reasoning applies to println! as well.
To explain why the named parameters syntax is restricted, I'll first note that the "{ident}" syntax existed before Rust 1.58 allowed it to access variables. It was originally designed to work like this:
println!("{name} bought {amount:.2} kg of {nouns}.",
name = "Richard",
amount = 5.0,
nouns = "apples",
);
So the syntax is not new, it was simply relaxed to look for other identifiers in-scope as a fallback if a named parameter wasn't passed to the macro.
They actually can't make the format macros take arbitrary expressions. Of course they can do whatever they like, however it would violate a restriction set elsewhere in the name of forward compatibility.
Let's say I tried to make a macro that worked similarly to the named parameter syntax but allowed for any expression while still allowing for other formatting parameters. Below is a trivial example but the compiler rejects it:
macro_rules! my_format {
($e:expr : $width:literal) => {
// ..
};
}
fn main() {
my_format!(expr:2);
}
error: `$e:expr` is followed by `:`, which is not allowed for `expr` fragments
--> src/main.rs:2:14
|
2 | ($e:expr : $width:literal) => {
| ^ not allowed after `expr` fragments
|
= note: allowed there are: `=>`, `,` or `;`
Why is this? If we look at Follow-set Ambiguity Restrictions:
The parser used by the macro system is reasonably powerful, but it is limited in order to prevent ambiguity in current or future versions of the language. In particular, in addition to the rule about ambiguous expansions, a nonterminal matched by a metavariable must be followed by a token which has been decided can be safely used after that kind of match.
As an example, a macro matcher like $i:expr [ , ] could in theory be accepted in Rust today, since [,] cannot be part of a legal expression and therefore the parse would always be unambiguous. However, because [ can start trailing expressions, [ is not a character which can safely be ruled out as coming after an expression. If [,] were accepted in a later version of Rust, this matcher would become ambiguous or would misparse, breaking working code. Matchers like $i:expr, or $i:expr; would be legal, however, because , and ; are legal expression separators. The specific rules are:
expr and stmt may only be followed by one of: =>, ,, or ;.
So the developers have not ruled out that : may be some expression trailer or joiner in the future and would like to reserve the syntax if needed. Even if you try to parse it with syn in a procedural macro to work around this restriction, it will actually try to parse as an ExprType (like foo: f64) which is part of the Type Ascription RFC. While that RFC was mostly implemented, it is set to be removed, but I mention it as an example of evolving syntax. If they were to allow the formatting argument to be an arbitrary expression, they'd have to stabilize that : is a valid expression separator.
Unless I'm missing some other syntax, : probably could be stabilized as a separator, and perhaps this is a motivating case. But only the future will tell.
Another hurdle that'd have to be dealt with is that blocks ({ ... }) are also expressions and yet there is an existing syntax for {{ and }} in the formatting macros; they are used for escaping. So if any expression were allowed, then "{{expr}}" would be ambiguous.
This maplit crate allows hashmap literals with => as a separator. I believe it's impossible to use macro_rules! to allow the : separator, but is it possible with a macro that processes a stream of tokens?
Is it impossible to add { key: value } style macros to the language, even in the compiler?
I suspect the problem is conflict with the : type operator, but I cannot construct an ambiguous example where it is impossible for the compiler to make a decision of which way to interpret :.
#kmdreko does a good job of explaining why you can't have an expression followed by a colon in a macro. However, you can work around that, by forcing the key to be a token tree rather than an expression:
macro_rules! hashmap{
( $($key:tt : $val:expr),* $(,)? ) =>{{
#[allow(unused_mut)]
let mut map = ::std::collections::HashMap::with_capacity(hashmap!(#count $($key),* ));
$(
#[allow(unused_parens)]
let _ = map.insert($key, $val);
)*
map
}};
(#replace $_t:tt $e:expr ) => { $e };
(#count $($t:tt)*) => { <[()]>::len(&[$( hashmap!(#replace $t ()) ),*]) }
}
playground
The drawback of taking this route, as opposed to a procedural macro, which probably could handle this pattern just fine, is that most complex expressions will have to be wrapped in parentheses or curly brackets. For example,
let map = hashmap!{ 2 + 2 : "foo" };
won't work, but
let map = hashmap!{ (2 + 2) : "foo" };
will.
If you were to replace the => with a : in the maplit! macro you'd get this error:
error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments
--> src/lib.rs:6:17
|
6 | ($($key:expr: $value:expr),*) => {
| ^ not allowed after `expr` fragments
|
= note: allowed there are: `=>`, `,` or `;`
The specific problem is that the macro accepts any arbitrary expression as the key type. You could still have the { key: value } syntax, but only if the key is an ident (for example) instead of an expr.
You can read the Follow-set Ambiguity Restrictions section of Macros by Example in the Rust Reference:
The parser used by the macro system is reasonably powerful, but it is limited in order to prevent ambiguity in current or future versions of the language.
[...]
expr and stmt may only be followed by one of: =>, ,, or ;.
That's not to say its necessarily ambiguous, though it may get confusing to parse in examples with a lot of :s like MyEnum::Variant: ::std::collections::Vec::new(). Its designed like that because only the documented tokens are guaranteed to indicate the end of an expression and other tokens may become ambiguous in the future.
You'll see though, that this only limits how macro_rules! patterns are handled and would not affect a procedural macro since you have free reign to decide how the tokens are parsed.
I tried to create a simple procedural macro using syn to aid in parsing expressions, but it chokes on the Expr : Expr syntax because it attempts to eagerly parse the first expression as a type ascription expression which is a yet-to-be-completed feature. This is the kind of future expansion that the macro_rules! guards against.
Going with a token tree and using parenthesis for ambiguity in Aiden4's answer is the way to go if you really want : instead of =>. This still could be done with a procedural macro, but it would not be as readily available and would be ambiguous if type ascription expressions gets added to the language.
I'm new to the Rust language and as I usually do when trying to pick up a language, I like to go through Euler Project questions. I want to get familiar with cargo and everything it offers, so I've created a cargo project called euler-project.
On startup, I want the program to ask the user which solution to run and have it run a function in the code that corresponds to the solution requested by the user.
I like to avoid huge chains of if / else if / else blocks, so I thought the match function would work well. Here's what I have.
use std::io;
fn main() {
// Solution selection. Every solution will be selectable here
let mut selection = String::new();
// Enum to hold every solved problem number for match control flow
enum Solutions {
P1, P2,
}
loop {
println!("Select the solution you would like to run. (Example: '32' would be Problem 32)");
io::stdin()
.read_line(&mut selection)
.expect("Input should be an integer.");
match selection {
Solutions::P1 => p1(),
Solutions::P2 => p2(),
}
}
}
fn p1() {
println!("p1")
}
fn p2() {
}
Currently this produces an error as follows:
...
21 | match selection {
| --------- this expression has type `std::string::String`
22 | Solutions::P1 => p1(),
| ^^^^^^^^^^^^^ expected struct `std::string::String`, found enum `main::Solutions`
What am I doing wrong here? If you know a better option for doing this type of control, please suggest it to me. I am also curious to know if there is anything similar to Python's interactive console mode that allows a user to run the code in the terminal, and to make function calls by simply typing function names and hitting enter.
You're comparing apples and oranges here. On the one hand, you have the string you have read from the user, and on the other hand you have values of type Solutions, but nowhere do you tell the compiler how to compare the two. The easiest way to do what you want is just to compare strings directly:
match selection.trim() {
"1" => p1(),
"2" => p2(),
_ => panic!("{} does not designate a valid problem!", selection),
}
Note the call to trim: the string returned by read_line includes the trailing newline, which you need to remove so that the comparisons will work.
Note also that you will need to call selection.clear() before read_line, otherwise the next iteration of the loop will append to the string instead of replacing it.
You can't match an enum with a String. You could try something like:
match selection.trim().parse::<i32>() {
Ok(x) if x == (Solutions::P1 as i32) => p1(),
Ok(x) if x == (Solutions::P2 as i32)=> p2(),
x => panic!{x},
}
Consider the following Rust code:
fn f() -> i32 {
loop {
println!("Infinite loop!");
}
println!("Unreachable");
}
This compiles (with a warning) and runs, despite the fact that the return type is wrong.
It would seem that the compiler is OK with the return type of () in the last line because it detects that this code is unreachable.
However, if we remove the last semicolon:
fn f() -> i32 {
loop {
println!("Infinite loop!");
}
println!("Unreachable")
}
Then the code no longer compiles, giving a type error:
error[E0308]: mismatched types
--> src/main.rs:14:5
|
14 | println!("Unreachable")
| ^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `()`
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
Why is this? Isn't the return type the same, (), in both of these code snipets?
Note: I'm interested in understanding why the Rust compiler behaves differently on these two examples, i.e. how the Rust compiler is implemented. I did not mean to ask a philosophical question about how it "should" behave, from the perspective of language design (I understand that such a question would probably be off-topic).
The return type in the first code block is actually ! (called never) because you have a loop that never exits (so rust gives you a warning saying it's unreachable). The full type would be:
fn f() -> !
I suspect ! is more like the 'bottom' type in Rust than anything else. In the second case, your function likely errors out in an earlier stage during type checking because of the mismatch between i32 and () before the compiler gets to the 'unreachability' analysis, like it does in the first example.
edit: as suggested, here is the relevant part of the rust book https://doc.rust-lang.org/book/ch19-04-advanced-types.html#the-never-type-that-never-returns
(Converting Sven's first comment into an answer)
The Rust compiler needs to infer a type for the function body. In the first case, there is no return expression, and apparently the compiler infers ! as the return type because of the infinite loop, which makes sense. In the second case, there's a return expression, so the type inference solver uses that to infer the type, which also makes sense.
I don't think this is specified in the language reference, nor do I think it matters in any way – just omit the unreachable statement and you'll be fine.
I have a piece of code like this:
use std::cell::RefCell;
use std::rc::Rc;
struct A(bool);
impl A {
fn get_ref(&self) -> &Rc<RefCell<bool>> {
&Rc::new(RefCell::new(self.0))
}
fn copy_ref(&self) -> &Rc<RefCell<bool>> {
Rc::clone(self.get_ref())
}
}
fn main() {
let a = A(true);
a.copy_ref();
}
and I received warning complaining about the Rc::clone function not getting a reference:
error[E0308]: mismatched types
--> src/main.rs:12:9
|
12 | Rc::clone(self.get_ref())
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found struct `std::rc::Rc`
|
= note: expected type `&std::rc::Rc<std::cell::RefCell<bool>>`
found type `std::rc::Rc<std::cell::RefCell<bool>>`
I have been working on this all night but I cannot get it to work. The method get_ref is already typed as returning &Rc<RefCell<bool>>, but why would the compiler give the error?
The error is not talking about the argument you put into Arc::clone(), but the whole expression Rc::clone(...) which has a different type (Rc<...>) than the return type of your function (&Rc<...>).
If you were passing a wrong argument to Rc::clone, it would like look this:
--> src/main.rs:13:19
|
13 | Rc::clone(false)
| ^^^^^ expected reference, found bool
|
= note: expected type `&std::rc::Rc<_>`
found type `bool`
So the naive way to fix the type error is to write &Rc::clone(...) instead. Then the last expression of your function has the same type as your function's declared return type. But as you will notice, you will get other errors afterwards.
Let's take a step back to see that your approach is flawed here. For the most important point, please see "Is there any way to return a reference to a variable created in a function?". Spoiler: you really don't want to. So constructs like your get_ref() just don't make sense, as you return a reference to a variable you create inside your function (a variable of type Rc).
In your case the direct solution is probably pretty simple: just remove the reference. Rc<T> is already a pointer/reference type, so there is no need (in general) to have a reference to it.
However, since you are using Rc, you are probably interested in reference counting. So in that case, you probably shouldn't create a new Rc every time the function is called. Otherwise you could end up with a bunch of Rcs with reference count 1, which is not really the point. So instead, your type A should already store an Rc<RefCell<bool>>.
But all I'm doing here is guessing what you actually want to do which is not clear from your question. Maybe you can ask a different question, or add the information to this question, or explain this in the comments.