Confusing unreachable pattern error - rust

I encountered a very confusing error message from the Rust compiler when using the match statement.
enum Name {
Known,
}
fn unreachable_pattern(n: Name) -> usize {
use Name::*;
match n {
Unknown => 1,
Known => 1,
}
}
The Rust compiler complains about an unreachable pattern:
error[E0001]: unreachable pattern
--> src/main.rs:10:9
|
10 | Known => 1,
| ^^^^^ this is an unreachable pattern
|
note: this pattern matches any value
--> src/main.rs:9:9
|
9 | Unknown => 1,
| ^^^^^^^
To a human, the real error is that Unknown is missing from the definition of Name, which is easier to spot when you do not have 40 other variants already.

This is actually a known issue at the moment; it is not a bug proper, but rather a quality of implementation issue.
The issue boils down to irrefutable matches, i.e.:
match variable {
1 => 2,
i => 2 * i
}
Here i is an irrefutable match, meaning it always matches, no matter the value of variable.
Well, we have the same issue with that weird report: because Unknown is unknown, it becomes a name for a variable in an irrefutable match! It is unintended, of course, but it makes perfect sense for the compiler.
The good news is that the compiler starts complaining as soon as the next attempted match, so you can easily find out which match is irrefutable.
A style-lint is expected which would help in this issue, it would report Unknown to be an improperly capitalized variable thus underlining that the compiler does not see it as an enum variant.
There are plenty of variations for this bug (see the duplicates), it might also be caused by improperly importing an enum for example and thus not having its variants in scope.

Related

Confusing error message on ? (unwrap operator)

I have this rust method error message:
error[E0277]: the `?` operator can only be used on `Option`s,
not `Result`s, in an async function that returns `Option`
I must admit, I often encounter Rust error messages which appear confusing to me, while to most other coders they make absolute sense.
So, I apologize in advance for posting this question.
First of all: what does that second comma in the error message mean? Should I read it as the following:
"If an async function call [within another function] returns an enum of the type Result then the ? operator can only be applied if, and only if, the respective [other] function also returns an enum of type Result and not an enum of type Option"
Pardon my verbose language. I hope I got my point across.
What also got me confused was the error message with that very same Reference i.e. error[E0277] , which is listed in the official rust error codes index, states:
"You tried to use a type which doesn't implement some trait in a place which expected that trait."
In which universe do these two error messages have anything in common, except for the identical reference number?
And here's the entire error block, which Rust produced:
error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in an async function that returns `Option`
--> src/utils/tokenizer.rs:72:73
|
70 | pub async fn clear(&self) -> Option<String> {
| _________________________________________________-
71 | | let mut conn = self.pool.get().await.unwrap();
72 | | let mut iter: redis::AsyncIter<i32> = conn.sscan("my_set").await?;
| | ^ use `.ok()?` if you want to discard the `Result<Infallible, Red
Error>` error information
73 | | while let Some(element) = iter.next_item().await {
... |
79 | | Some(String::from("A"))
80 | | }
| |_____- this function returns an `Option`
|
= help: the trait `FromResidual<Result<Infallible, RedisError>>` is not implemented for `std::option::Option<std::string::String>`
= help: the following other types implement trait `FromResidual<R>`:
<std::option::Option<T> as FromResidual<Yeet<()>>>
<std::option::Option<T> as FromResidual>
For more information about this error, try `rustc --explain E0277`.
What is the canonical error message, the one from the error code index page or the one, the compiler produces?
Your function clear returns Option<String> but the function conn.sscan("my_set").await?; returns a Result. You cannot use ? to propagate the result because that doesn't match the return type of clear(&self) -> Option<String>.
You will need explicitly handle the Result or convert it to an Option<String>. You could also try adding .ok()? per the compiler hint and see what that gets you.
Whatever you pick the important thing is just to make sure that the return type matches what your ? operator is unwrapping.
Yes, your interpretation is correct, although to be pedantic the error says the opposite:
"If an async function returns Option then the ? operator can only be applied to expressions that has the type Option, and not Result".
And if you ask why on earth this has error code E0277? Well, this is because in order to be propagated using ? a type has to implement the (experimental) Try trait with some combination of type parameters and associated types corresponding to some other combination of the return type of the function. So, in essence, an incompatible type in ? boils down to an unsatisfied trait bound. But unless you're interested in the inner workings of ?, this is not really important.

Not understanding the match function of a HashMap.entry()

I am just starting to learn Rust. The entry method an Entry enum, which can be accessed via the match function. But then I don't get why it doesn't compile and it panics.
Can someone explain please what I'm understanding wrong?
let mut scores: HashMap<String, u32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let a = 10;
match scores.entry("poneyland".to_string()) {
Occupied(o) => o.remove_entry(),
Vacant(o) => o.insert(37),
}
But then I don't get why it doesn't compile and it panics.
It can't both fail to compile and panic, since panic is a runtime behaviour. Either way, when there is an error it is usually a good idea to post it, as that makes diagnostic much easier, especially in Rust which generally has pretty helpful error messages.
In this case, the compilation error tells you exactly what's what:
error[E0308]: `match` arms have incompatible types
--> src/main.rs:15:22
|
13 | / match scores.entry("poneyland".to_string()) {
14 | | Occupied(o) => o.remove_entry(),
| | ---------------- this is found to be of type `(String, u32)`
15 | | Vacant(o) => o.insert(37),
| | ^^^^^^^^^^^^ expected tuple, found `&mut u32`
16 | | }
| |_____- `match` arms have incompatible types
|
= note: expected tuple `(String, u32)`
found mutable reference `&mut u32`
You pretty much just need to read the message, and possibly the expanded document.
What it boils down to is a rust match is an expression, meaning it has a value of a specific type, meaning all arms must have values of the same type (or a compatible type e.g. !).
Here, as the compiler error tells you, in one arm you're calling remove_entry which returns a tuple of (key, value), while in the other arm you're calling insert which returns a mutable reference to the inserted value.
These types are obviously not compatible.
The easiest way to fix this is to make them compatible by... suppressing the results entirely since you're not using them: convert the arm expressions to arm blocks and terminate the expressions with ;. That makes the expressions into statements and suppresses their value, implicitly returning () instead:
match scores.entry("poneyland".to_string()) {
Occupied(o) => {
o.remove_entry();
}
Vacant(o) => {
o.insert(37);
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1b885daa2a4eaa6d9d7c366753f6c2aa

Using macro output as a method name

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)) ,)*
}
}
}

Understanding Ok with error propagation operator?

I've seen this pattern on more than one occasion:
fn f() -> Result<..., ...> {
...
Ok(expression()?)
}
Specifically, I find the order Ok(expression()?) confusing. What's the return type of expression()? and is there an interplay between Ok and this type? It seems, the function Ok must do more than just capturing the value. But how can it force a return with an error, if it's the last expression wrapping the return type of expression()?.
The ? operator either returns (as in the return statement) the error variant, or extracts the value from the Ok variant. Thus Ok(expression()?) is more or less equal to:
// This pattern is so common in rust, that the language designers
// introduced the `?` to reduce the amount of boilerplate needed
let result = expression();
let r = match result{
Err(e) => return Err(e.into()), // tries to convert the error into the required type if necessary (and if possible)
Ok(r) => r
}
Ok(r)
So basically Ok(expression()?) can be simplified to just
expression() if its Err variant matches the one from the function definition.
Historical context:
Extracting the Ok() variant and propagating the Err variant is a very common pattern in rust. In order to reduce the amount of boilerplate code, the rust team introduced the try!() macro; But it's so clumsy and does not really work well with method chaining. The rust team introduced the ? operator, which is doing absolutely the same thing, except for it's not so intrusive as try!()
? just leverages the error returned by expression if it exists or unwraps the value from within the returned Ok if not. Ok(expression()?) could be just expresion() and clippy would warn you about that:
warning: question mark operator is useless here
--> src/lib.rs:8:5
|
8 | Ok(expression()?)
| ^^^^^^^^^^^^^^^^^ help: try: `expression()`
|
= note: `#[warn(clippy::needless_question_mark)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
Playground
You can follow the documentation for further information
As per the comments. Notice that ? is useful if the error types differ but you have an implementation of From between error types or you use Box<dyn Error> as return type.
In which case you can either, bubble up the error if it is an error, otherwise rewrap the value with an Ok
Ok(expression()?)
or, map your error type into the correct error type with map_err
expression.map_err(|e| ...)

Factorial if statement expect '()' found integer

I'm new to rust (coming from programming in c/c++ and python) so to learn I'm writing some basic functions. Below I have a factorial function that takes in a signed integer and has two if checks for it.
fn factorial(x: i32) -> i32 {
let result = if x > 1 {
x * factorial(x-1)
} else if x <= 1 {
1
};
result
}
To my knowledge, the if and else-if blocks should handle every case for it. However, when compiling it throws the following error:
error[E0317]: `if` may be missing an `else` clause
--> src/main.rs:22:12
|
22 | } else if x <= 1 {
| ____________^
23 | | 1
| | - found here
24 | | };
| |_____^ expected `()`, found integer
|
= note: `if` expressions without `else` evaluate to `()`
= help: consider adding an `else` block that evaluates to the expected type
error: aborting due to previous error
For more information about this error, try `rustc --explain E0317`.
error: could not compile `functions`
If I replace the else-if with just an else, it compiles just fine. Why do I need to replace it with an else? Shouldn't the previous else-if be good enough?
As the error message says, if an if expression doesn't have an else then the type of the expression is (). This is because the expression can only have one type, and there is no sensible default value in the general case for if the condition evaluated to false - that's what else is for!
In your case, the compiler could have figured out that the two predicates are in fact exhaustive. It doesn't, and that really is just how it is. If the compiler could detect exhaustiveness in this case it would be weird if it couldn't also detect it in other "obvious" cases. But predicates can be arbitrary expressions and it would be impossible to implement the check in the general case.
In this example, the compiler would have to analyse the body of the random function to know if the predicates are exhaustive or not:
// random(x) might return a different value each time it's called
if random(x) > 1 {
1
} else if random(x) <= 1 {
2
} // Not exhaustive!
Being consistent seems like the best choice for the language, since you can always add an else at the end.

Resources