Skip to content

Regression: format_args!() no longer lifetime-extends some temporaries. #145422

Closed

Description

@theemathas

Note that unlike the other similar issues, this bug does not involve if let, and therefore will not be fixed by #145342.

I tried this code:

#[derive(Debug)]
struct Thing;

#[derive(Debug)]
struct Ref<'a>(&'a Thing);
impl Drop for Ref<'_> {
    fn drop(&mut self) {}
}

fn new_thing() -> Thing {
    Thing
}

fn new_ref(x: &Thing) -> Ref<'_> {
    Ref(x)
}

pub fn foo() {
    let _x = format_args!("{:?}, {:?}", 1, new_ref(&new_thing()));
}

The code compiled in version 1.88.0. However, in version 1.89.0, I get the following compile error:

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:19:53
   |
19 |     let _x = format_args!("{:?}, {:?}", 1, new_ref(&new_thing()));
   |                                            ---------^^^^^^^^^^^- - temporary value is freed at the end of this statement
   |                                            |        |
   |                                            |        creates a temporary value which is freed while still in use
   |                                            a temporary with access to the borrow is created here ...
20 | }
   | - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `Ref`
   |
   = note: consider using a `let` binding to create a longer lived value

For more information about this error, try `rustc --explain E0716`.

I'm not sure how important this is though, since as far as I can tell, there is no way to actually be able to use the value of _x in version 1.88.0.

Presumably regressed in #140748

@rustbot labels +regression-from-stable-to-stable +F-super_let +A-destructors

Meta

Regression from stable rust version 1.88.0 to 1.89.0.

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 15, 2025
  2. rustbot commented on Aug 15, 2025

    @rustbot
  3. added
    A-destructorsArea: Destructors (`Drop`, …)
    regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Aug 15, 2025
  4. theemathas commented on Aug 15, 2025

    @theemathas
    ContributorAuthor

    @dianne's explanation of the issue, copied from #145342 (comment):

    Oh nice, I think that's a different bug! Lifetime extension is happening there. Here's the pretty-printed HIR:

    fn foo() {
        let _x =
            {
                super let args = (&1, &new_ref(&new_thing()));
                super let args =
                    [format_argument::new_debug(args.0),
                            format_argument::new_debug(args.1)];
                format_arguments::new_v1(&["", ", "], &args)
            };
    }

    Unlike with write!, which doesn't put the super lets in a position where they can be lifetime-extended, these super lets are extended by the outer let, which ends up tangling up the drop order (I assume the tangled drop order is a bug).

    This PR only changes non-lifetime-extended super let, so that regression is unaffected.

  5. added
    T-libs-api[DEPRECATED; DO NOT USE]
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Aug 15, 2025
  6. dianne commented on Aug 15, 2025

    @dianne
    Member

    Additional diagnosis: this is maybe to be expected from the current expansion of format_args!. The inner new_thing() temp doesn't get lifetime-extended, whereas the outer new_ref(&that_temp) does. It's like normal let in that way, when in an extending expression in a parent let initializer (as in all these examples); pulling the super lets out of the block gives us

    let args = (&1, &new_ref(&new_thing()));
    // ...

    which results in the same error.

    With just one format argument, we run into a similar issue:

    let _x =
        {
            super let args =
                [format_argument::new_debug(&new_ref(&new_thing()))];
            format_arguments::new_v1(&[""], &args)
        };

    The format arguments can be assigned to _x, but _x can't be used because the temporaries aren't lifetime-extended.

    So my feeling is this may be a limitation of format_args!'s expansion and of let's lifetime extension rules, rather than a bug in the implementation of super let. This didn't come up in the issue with assigning format arguments in if let, because temporaries from if let's scrutinee live into the success block.

  7. added
    T-langRelevant to the language team
    I-lang-nominatedNominated for discussion during a lang team meeting.
    P-lang-drag-1Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang
    I-lang-radarItems that are on lang's radar and will need eventual work or consideration.
    and removed
    I-lang-nominatedNominated for discussion during a lang team meeting.
    P-lang-drag-1Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang
    on Aug 15, 2025
  8. removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 15, 2025
  9. dianne commented on Aug 16, 2025

    @dianne
    Member

    Another case of format_args! being inconsistent with lifetime extension (same underlying cause) (playground):

    // This works; the intermediate `super let` from having multiple arguments
    // causes the `Droppy` temporary's lifetime to be extended:
    let x = format_args!("{:?}, {:?}", 1, Droppy);
    x;
    // This doesn't work; no intermediate `super let` means no extension:
    let y = format_args!("{:?}", Droppy);
    y;
  10. apiraino commented on Aug 18, 2025

    @apiraino
    Contributor

    Assigning priority (discussion on Zulip).

    @rustbot label -I-prioritize +P-medium

  11. added and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Aug 18, 2025
  12. self-assigned this
    on Aug 20, 2025
  13. theemathas commented on Aug 26, 2025

    @theemathas
    ContributorAuthor

    To make it more fun, the effect of an "optimization" is visible to users:

    // This works. {:?} is not optimized.
    let x = format_args!("{:?}, {:?}", 1, Droppy);
    x;
    // This fails. {} with a literals is optimized so the
    // format macro becomes effectively:
    // format_args!("1, {:?}", Droppy)
    let y = format_args!("{}, {:?}", 1, Droppy);
    y;
  14. m-ou-se commented on Aug 26, 2025

    @m-ou-se
    Member

    So my feeling is this may be a limitation of format_args!'s expansion and of let's lifetime extension rules, rather than a bug in the implementation of super let.

    Yup, this is expected. It is technically a breaking change that it went from 'using it is an error' to 'defining it is an error', but it's extremely unlikely to result in any real life regressions.

    What is not expected is that it behaves differently with one or with two+ arguments. I'll fix that.

  15. m-ou-se commented on Aug 26, 2025

    @m-ou-se
    Member

    What is not expected is that it behaves differently with one or with two+ arguments. I'll fix that.

    Opened #145880 for that.

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

Metadata

Metadata

Assignees

Labels

A-destructorsArea: Destructors (`Drop`, …)A-fmtArea: `core::fmt`A-temporary-lifetime-extensionArea: temporary lifetime extensionC-bugCategory: This is a bug.F-super_letit's super, let's go!I-lang-radarItems that are on lang's radar and will need eventual work or consideration.P-mediumMedium priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-libs-api[DEPRECATED; DO NOT USE]regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions