Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my Rust macro not work due to temporary value drop when updating to the 2024 edition?

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;
}

Rust Playground

In Edition 2021 this worked but in Edition 2024 I get an error:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:31:19
   |
8  |     expect(&$subject).with_line(line)
   |                                     - temporary value is freed at the end of this statement
...
31 |   let s = expect!(String::new()).subject;
   |           --------^^^^^^^^^^^^^---------
   |           |       |
   |           |       creates a temporary value which is freed while still in use
   |           borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

I understand the problem. Is it possible to rewrite the macro (or the Subject) such that the example in main compiles again?

like image 495
CoronA Avatar asked Nov 16 '25 06:11

CoronA


1 Answers

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 }
like image 82
kmdreko Avatar answered Nov 18 '25 21:11

kmdreko



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!