Skip to content

Conversation

aytey
Copy link

@aytey aytey commented Aug 1, 2025

In the 2024 edition of Rust, serde's macros for serialize_with can lead to a temporary lifetime error such as:

error[E0716]: temporary value dropped while borrowed
 --> my-binary/src/main.rs:6:10
  |
6 | #[derive(MyDerive)]
  |          ^^^^^^^-
  |          |      |
  |          |      temporary value is freed at the end of this statement
  |          creates a temporary value which is freed while still in use
  |          borrow later used by call
  |          in this derive macro expansion
  |
 ::: /private/tmp/life/my-project/my-macro/src/lib.rs:6:1
  |
6 | pub fn my_derive(_input: TokenStream) -> TokenStream {
  | ---------------------------------------------------- in this expansion of `#[derive(MyDerive)]`
  |
  = note: consider using a `let` binding to create a longer lived value

This is because the macro code takes a reference to struct inside of a block, which then goes out of scope when serde passes it to a function.

To resolve this, we move the reference to outside of the block, to ensure that the lifetime extends into the function call.

In the 2024 edition of Rust, `serde`s macros for `serialize_with` can
lead to a temporary lifetime error such as:

```
error[E0716]: temporary value dropped while borrowed
 --> my-binary/src/main.rs:6:10
  |
6 | #[derive(MyDerive)]
  |          ^^^^^^^-
  |          |      |
  |          |      temporary value is freed at the end of this statement
  |          creates a temporary value which is freed while still in use
  |          borrow later used by call
  |          in this derive macro expansion
  |
 ::: /private/tmp/life/my-project/my-macro/src/lib.rs:6:1
  |
6 | pub fn my_derive(_input: TokenStream) -> TokenStream {
  | ---------------------------------------------------- in this expansion of `#[derive(MyDerive)]`
  |
  = note: consider using a `let` binding to create a longer lived value
```

This is because the macro code takes a reference to struct inside of a
block, which then goes out of scope when `serde` passes it to a
function.

To resolve this, we move the reference to outside of the block, to
ensure that the lifetime extends into the function call.

Signed-off-by: Andrew V. Teylu <[email protected]>
@aytey
Copy link
Author

aytey commented Aug 1, 2025

For reference, a minimal example that can reproduce the problem is something like:

use serde::{Serialize, Serializer};

#[derive(Serialize)]
struct S {
    #[serde(serialize_with = "f")]
    x: u8,
}

fn f<T, S>(_: &T, s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    s.serialize_unit()
}

which generates code that is (similar to):

use std::marker::PhantomData;

trait Error {}

fn takes_reference<T>(_: &T) {}

struct S {
    x: u8,
}

impl S {
    fn method(&self) {
        takes_reference(
            {
                struct Wrapper<'a> {
                    value: &'a u8,
                    phantom: PhantomData<S>,
                }
                &Wrapper {
                    value: &self.x,
                    phantom: PhantomData,
                }
            }
        );
    }
}

The issue is that the reference is taken against Wrapper inside the block {}, but the temporary value's lifetime expires before being passed to takes_reference. The fix moves the reference outside the block.

In actual serde code, the function is serialize_field and the structure is __SerializeWith, but the bug is identical.

If you compile this with --edition 2024, you get:

error[E0716]: temporary value dropped while borrowed
  --> unit.rs:19:18
   |
13 |           takes_reference(
   |           --------------- borrow later used by call
...
19 |                   &Wrapper {
   |  __________________^
20 | |                     value: &self.x,
21 | |                     phantom: PhantomData,
22 | |                 }
   | |                 ^
   | |                 |
   | |_________________temporary value is freed at the end of this statement
   |                   creates a temporary value which is freed while still in use

Swapping all of serde to use 2024 (along with the minimal example I gave previously), gives:

error[E0716]: temporary value dropped while borrowed
 --> my-binary/src/main.rs:6:10
  |
6 | #[derive(MyDerive)]
  |          ^^^^^^^-
  |          |      |
  |          |      temporary value is freed at the end of this statement
  |          creates a temporary value which is freed while still in use
  |          borrow later used by call
  |          in this derive macro expansion
  |
 ::: /private/tmp/life/my-project/my-macro/src/lib.rs:6:1
  |
6 | pub fn my_derive(_input: TokenStream) -> TokenStream {
  | ---------------------------------------------------- in this expansion of `#[derive(MyDerive)]`
  |
  = note: consider using a `let` binding to create a longer lived value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant