rustlifetimerust-macros

Rust macro does not work since Edition 2024 because of temporary value drop


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, 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?


Solution

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