Description
Not sure if this should be considered a bug or a diagnostic issue.
Having a const left_val
or const right_val
declared breaks assert_eq!
. This has to do with its expansion and Rust's rules for macro hygiene: https://sabrinajewson.org/blog/truly-hygienic-let
Consider this code
fn main() {
let x: u8 = 0;
assert_eq!(x, 0);
}
according to cargo expand
it expands to
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
let x: u8 = 0;
match (&x, &0) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(
kind,
&*left_val,
&*right_val,
::core::option::Option::None,
);
}
}
};
}
Since assert_eq!
wants to use the value of the provided expressions twice (once for comparison, once for printing the result on failure), but it only wants to evaluate each expression once, it does a match
to bind them to a pattern (left_val, right_val)
. However, having a const
named left_val
or right_val
in scope changes the meaning of the pattern.
fn main() {
let x: u8 = 0;
const left_val: i8 = -123;
assert_eq!(x, 0);
}
error[E0308]: mismatched types
--> src/main.rs:4:5
|
3 | const left_val: i8 = -123;
| ------------------ constant defined here
4 | assert_eq!(x, 0);
| ^^^^^^^^^^^^^^^^
| |
| expected `&u8`, found `i8`
| this expression has type `(&u8, &{integer})`
| `left_val` is interpreted as a constant, not a new binding
| help: introduce a new binding instead: `other_left_val`
|
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0614]: type `i8` cannot be dereferenced
--> src/main.rs:4:5
|
4 | assert_eq!(x, 0);
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
The error message, admittedly, is not very helpful.
Thankfully, you can't use this to make assert_eq
pass/fail when it shouldn't. The worst you can achieve is a cryptic error message from the compiler. I think. So this "bug" is not really exploitable, plus chances of accidentally breaking this are probably pretty low (const
s are usually named in UPPER_CASE
in Rust), but the diagnostic is admittedly not very helpful.
The article I've linked above (https://sabrinajewson.org/blog/truly-hygienic-let) offers a potential solution for this. TL;DR: due to shadowing shenanigans having a function named left_val
will prevent left_val
from being interpreted as a const in patterns.
@rustbot label A-macros A-diagnostics C-bug D-confusing D-terse
Activity
assert_eq
#131443GrigorenkoPV commentedon Oct 9, 2024
Unfortunately if we do this
then this starts to compile
so still not 100% hygienic.
GrigorenkoPV commentedon Oct 9, 2024
I see 3 ways of dealing with this:
oriongonza commentedon Oct 9, 2024
If this hasn't been a problem for the past 9 years we could change the name of
left_val
andright_val
to__left_val
and__right_val
(like c++ does) to make it even less of a problemzachs18 commentedon Oct 9, 2024
We could have
assert_eq!
(and other macros) useleft_val@_
(etc) to give an explicit "match bindings cannot shadow constants" error if someone has aconst left_val
in scope (the left hand side of a@
pattern is (currently) always an identifer pattern; it is not superseded by path/const patterns like "bare" identifier patterns are).GrigorenkoPV commentedon Oct 9, 2024
Well, technically C++ also forbids user-created identifiers to start with
_
IIRC, but yeah.This sounds like a good option. Not ideal, but given it is an obscure issue in the first place, probably good enough.
danielhenrymantilla commentedon Oct 11, 2024
😰
13 remaining items