In my old code base I had a macro expect
that captured an argument as reference and returned a lifetimed struct containing this reference for further processing.
#[macro_export]
macro_rules! expect {
(&$subject:expr) => {
expect!($subject)
};
($subject:expr) => {{
let line = line!();
expect(&$subject).with_line(line)
}};
}
pub fn expect<S>(subject: &S) -> Subject<S> {
Subject {
subject
}
}
pub struct Subject<'a, S> {
subject: &'a S
}
impl <'a,S> Subject<'a,S> {
pub fn with_line(self, line:u32) -> Self {
// do something with line
self
}
}
fn main() {
let s = expect!(String::new()).subject;
let sref = "s";
let s = expect!(&sref).subject;
}
In Edition 2021 this worked, in Edition 2024 I got: "temporary value dropped while borrowed".
I understand the problem. Is it possible to rewrite the macro (or the Subject) such that the example in main
compiles again?
The problem is your macro expands to a block expression that returns a temporary reference - the macro generates { let line = line!(); expect(&String::new()).with_line(line) }
.
The rules around temporaries within blocks did change in the 2024 edition. In 2021 and prior, the scope of temporaries in the tail expression of a block were promoted to the outer statement. But in 2024 they don't. From the guide:
// This example works in 2021, but fails to compile in 2024.
fn main() {
let x = { &String::from("1234") }.len();
}
Note that even in 2021 the resulting reference is not usable beyond the statement:
let s = expect!(String::new()).subject;
println!("{s}");
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:23:19
|
23 | let s = expect!(String::new()).subject;
| ^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
24 | println!("{s}");
| --- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
Regardless, if you want the same temporary scoping as before just remove the extra {}
s.
($subject:expr) => { // <--- only one {
expect(&$subject).with_line(line!())
}; // <--- only one }