diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 108cd3cd98eb5..a23aa0d877018 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -369,7 +369,8 @@ macro_rules! dbg { /// E.g. `dbg_internal!(() () (1, 2))` expands into /// ```rust, ignore /// match (1, 2) { -/// (tmp_1, tmp_2) => { +/// args => { +/// let (tmp_1, tmp_2) = args; /// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */); /// (tmp_1, tmp_2) /// } @@ -385,7 +386,9 @@ pub macro dbg_internal { // of temporaries - https://stackoverflow.com/a/48732525/1063961 // Always put the arguments in a tuple to avoid an unused parens lint on the pattern. match ($($processed,)+) { - ($($bound,)+) => { + // Move the entire tuple so it doesn't stick around as a temporary (#154988). + args => { + let ($($bound,)+) = args; $crate::eprint!( $crate::concat!($($piece),+), $( diff --git a/library/std/src/macros/tests.rs b/library/std/src/macros/tests.rs index db2be925ff30a..230bfdf3c9836 100644 --- a/library/std/src/macros/tests.rs +++ b/library/std/src/macros/tests.rs @@ -1,5 +1,7 @@ // ignore-tidy-dbg +use core::fmt::Debug; + /// Test for : /// `dbg!` shouldn't drop arguments' temporaries. #[test] @@ -11,3 +13,25 @@ fn no_dropping_temps() { *dbg!(0, &temp()).1; *dbg!(0, &temp(), 2).1; } + +/// Test for : +/// `dbg!` shouldn't create a temporary that lives past its invocation. +#[test] +fn no_leaking_internal_temps_from_dbg() { + #[derive(Debug)] + struct Foo; + + #[derive(Debug)] + struct Bar<'a>(#[allow(unused)] &'a Foo); + impl Drop for Bar<'_> { + fn drop(&mut self) {} + } + + let foo = Foo; + let bar = Bar(&foo); + // If `dbg!` creates a `(Bar<'_>,)` temporary that lasts past its expansion, this will fail + // to compile, because it will be dropped after `foo`, which it borrows from. The tuple + // mimics the drop order of block tail expressions before Rust 2024: first the result of `dbg!` + // is dropped, then `foo`, then any temporaries left over from `dbg!` are dropped, if present. + (drop(dbg!(bar)), drop(foo)); +}