Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow storing format_args! in a let binding #92698

Open
piegamesde opened this issue Jan 9, 2022 · 17 comments · May be fixed by #139135
Open

Allow storing format_args! in a let binding #92698

piegamesde opened this issue Jan 9, 2022 · 17 comments · May be fixed by #139135
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one.

Comments

@piegamesde
Copy link
Contributor

piegamesde commented Jan 9, 2022

At the moment, we cannot store the result of a format_args! in a value:

// Okay, we can somehow store it in a variable
let foo = format_args!("{} foo {:?}", 1, 2);

// But once we try to use it, we get
// "error[E0716]: temporary value dropped while borrowed"
// "note: consider using a `let` binding to create a longer lived value"
// "note: this error originates in the macro `format_args`"
println!("{}", foo);

// Using it directly is okay though
println!("{}", format_args!("{} foo {:?}", 1, 2));

// We can get pretty creative within the "direct usage" constraints :D
let foo = match format_args!("{} foo {:?}", 1, 2) {
    foo => {println!("{}", foo);},
};

// But even if we manage to store the value in a struct, …
struct Foo<'a> {foo: std::fmt::Arguments<'a> }
let foo = Foo { foo: format_args!("{} foo {:?}", 1, 2) };

// … we still cannot make use of it outside of the scope it was created in
println!("{}", foo.foo); // <- error[E0716]: temporary value dropped while borrowed

The list of confused or annoyed users by this is rather long:

I understand if the format_args! macro cannot be changed, but then please provide an alternative way of building fmt::Arguments without that restriction. Even a small performance overhead (for example cloning the values) would be an improvement compared to the common workarounds that have to be used otherwise.

@dtolnay
Copy link
Member

dtolnay commented Jan 18, 2022

This would be fixed by this accepted RFC: #15023.

@HindrikStegenga
Copy link

So I ran into this issue, but at the same time I managed to find a rather nifty workaround.
Basically it works if you use an anonymous closure and directly invoke it. (In my case I needed the length of the outputted string)

        (|buf : &mut fmt::Formatter, args : std::fmt::Arguments<'_>| {
            let i = args.to_string().len();
            buf.write_fmt(args)?;

            for _ in 0..(128 - i) {
                write!(buf, " ")?;
            }
            writeln!(buf, "{}:{}", file, line)
        })(buf, format_args!("[{}][{}] - {}", level, target, args))

For me this works and compiles on stable 1.61.

@piegamesde
Copy link
Contributor Author

Yes, this looks pretty much like the same hack fern is using with its FormatCallback.

@albel727
Copy link

albel727 commented Aug 9, 2022

@HindrikStegenga

(In my case I needed the length of the outputted string)

An exercise in futility, since you're calling args.to_string(), i.e. allocating a temporary string with the output anyway. You could've just called format!() right away and reused that same string for the rest of your work with buf.write_str(), instead of bothering with format_args!() and then redoing the formatting work a second time with buf.write_fmt().

If you really want to get the length of the outputted string without allocating, you could try calculating it manually, e.g. level.len() + target.len() + args.len() + 7. Or, more generally, you could make your implementor of std::fmt::Write, that would sum the lengths of the received strings in its write_str(). You could then feed your args to it with std::fmt::write().

Example in Playground

@tmccombs
Copy link
Contributor

tmccombs commented Dec 24, 2022

At the very least this surprising behavior should be better documented. And maybe, the error message should give a better recommendation of what to do when this happens for format_args!.

The suggestion of changing let args = format_args!("{}", thing); to

let binding = format_args!("{}", thing);
let args = binding;

isn't really helpful.

@Dylan-DPC Dylan-DPC added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Feb 16, 2023
@piegamesde
Copy link
Contributor Author

With the changes made to format_args! in #106745, would it allow us to fix this issue?

@m-ou-se
Copy link
Member

m-ou-se commented Apr 24, 2023

With the changes made to format_args! in #106745, would it allow us to fix this issue?

Nope, that's unrelated. format_args's expansion includes temporaries whose lifetimes need to be extended. We currently don't have a flexible way to do that. Temporary lifetime extension only applies to very few types of expressions, which don't include function calls. See also some related thoughts here on zulip: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/design.20meeting.202023-03-15.3A.20temporary.20lifetimes/near/351415051

@Stargateur
Copy link
Contributor

Stargateur commented May 2, 2023

At the very least this surprising behavior should be better documented. And maybe, the error message should give a better recommendation of what to do when this happens for format_args!.

The suggestion of changing let args = format_args!("{}", thing); to

let binding = format_args!("{}", thing);
let args = binding;

isn't really helpful.

Yes the error message is very misleading and doesn't make sense. We can add this stackoverflow question to the list of confused people.

We could at least link this issue in the documentation of format_args

@daniel-pfeiffer
Copy link

To make a deeply nested format_args more readable, I want to pull it apart

let y = format_args!("<{x}>");
println!("{y}");

As noted above, this gives

6 | let y = format_args!("<{x}>");
  |         ^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
  |         |
  |         creates a temporary value which is freed while still in use
7 | println!("{y}");
  |           --- borrow later used here
  |
  = note: this error originates in the macro `format_args` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using a `let` binding to create a longer lived value
  |
6 + let binding = format_args!("<{x}>");
7 ~ let y = binding;

Weird suggestion, but if I follow it

let binding = format_args!("<{x}>");
let y = binding;
println!("{y}");

It chokes on its own previous suggestion, giving an even weirder one, as it doesn't see binding is already used

6 | let binding = format_args!("<{x}>");
  |               ^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
  |               |
  |               creates a temporary value which is freed while still in use
7 | let y = binding;
  |         ------- borrow later used here
  |
  = note: this error originates in the macro `format_args` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using a `let` binding to create a longer lived value
  |
6 + let binding = format_args!("<{x}>");
7 ~ let binding = binding;

So I thought: if some temporary is too short lived, let me turn it all into one statement. Essentially like @HindrikStegenga's passing it to a closure, but in natural order

if let y = format_args!("<{x}>") {
    println!("{y}");
}

Even though this works as desired, it gives

6 | if let y = format_args!("<{x}>") {
  |    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`
  = note: `#[warn(irrefutable_let_patterns)]` on by default

@daniel-pfeiffer
Copy link

daniel-pfeiffer commented Aug 12, 2023

Until this gets solved, I wrapped @HindrikStegenga's closure in natural syntax:

macro_rules! let_workaround {
    (let $name:ident = $val:expr; $($rest:tt)+) => {
        let f = |$name| { // naming closure avoids #[allow(clippy::redundant_closure_call)]
            let_workaround! { $($rest)+ }
        };
        f($val)
    };
    ($($rest:tt)+) => { $($rest)+ }
}

fn main() {
    let (a, b, c) = (1, 2, 3);
    let f_ab = let_workaround! {
        let f_a = format_args!("a {a}");
        let f_b = format_args!("b {b}");
        format!("{f_a}, {f_b}") // hidden closure can't return format_args 😟
    }; // return value: semicolon on outer statement
    let_workaround! {
        let f_c = format_args!("c {c}");
        let f_abc = format_args!("{f_ab}, {f_c}");
        println!("{f_abc}");
    } // no return value: no semicolon on block
    println!("done");
}

What this can't solve, is the lack of ? :. if else just isn't equivalent, as format_args! also can't be returned from a block. E.g. you can't choose efficient low-level formatting at runtime:

if something { format_args!("a {a}") }
else { format_args!("b {b}") }

@daniel-pfeiffer
Copy link

daniel-pfeiffer commented Aug 16, 2023

Turns out clippy doesn't like this. What clippy misses here, is that this currently seems to be the only way to extend the temp lifetimes. So clippy is wrong. 👎

Edit 1: Added an annotation above, to silence this clippy warning. Or try @dtolnay's workaround below. However that's semantically the same as my if let (on June 14th.) So sooner or later it may also warn that the match is useless.

Edit 2: Changing the above again, as I didn't want the annotation to also be on the body of the closure (where it might be valid for some other usage). By splitting it (semantically the same) into a let and a call, clippy shut up on its own for now. Note that even though the braces are macro syntax and not a block, f still does not leak out (because of partial hygiene.)

@dtolnay
Copy link
Member

dtolnay commented Aug 16, 2023

Use match:

macro_rules! let_workaround {
    (let $name:ident = $val:expr; $($rest:tt)+) => {
        match $val {
            $name => {
                let_workaround! { $($rest)+ }
            }
        }
    };
    ($($rest:tt)+) => { $($rest)+ }
}

@quaternic
Copy link

@daniel-pfeiffer

What this can't solve, is the lack of ? :. if else just isn't equivalent, as format_args! also can't be returned from a block. E.g. you can't choose efficient low-level formatting at runtime:

if something { format_args!("a {a}") }
else { format_args!("b {b}") }

There is a workaround for that:

macro_rules! select {
    ($cond:expr, $iftrue:expr, $iffalse:expr) => {
        'outer:  {
            (
                'inner: {
                    if $cond { break 'inner }
                    break 'outer $iffalse
                },
                $iftrue
            ).1
        }
    }
}

To clarify the structure, that's essentially like

let tuple = (
    if condition { break iftrue } ,
    iffalse
);
break tuple.1;

or just

if condition {
    break iftrue
}
break iffalse

so select!(cond, A, B) means the same as if cond { A } else { B } but without the separate drop scopes for the branches.

In combination with the above variant of let_workaround from @dtolnay, you can write stuff like

let_workaround! {
    let ab = select!(a < b, format_args!(" < {b}"),
             select!(a > b, format_args!(" > {b}"),
                            format_args!("")));
    let bc = select!(b < c, format_args!(" < {c}"),
             select!(b > c, format_args!(" > {c}"),
                            format_args!("")));
    let ca = select!(c < a, format_args!(" < {a}"),
             select!(c > a, format_args!(" > {a}"),
                            format_args!("")));
    let min = a.min(b).min(c);
    let f_abc = select!(min == a, format_args!("{a}{ab}{bc}{ca}"),
                select!(min == b, format_args!("{b}{bc}{ca}{ab}"),
                select!(min == c, format_args!("{c}{ca}{ab}{bc}"),
                                  unreachable!())));
    write!(f, "{f_abc}")
}

https://godbolt.org/z/EcKqKTKGq

(This came up in related discussion in https://internals.rust-lang.org/t/format-args-with-long-lifetimes/19494)

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 15, 2024
Add known issue of let binding to format_args doc

Simply add doc about rust-lang#92698.

 `@rustbot` label +T-rustdoc -T-libs

 r? `@GuillaumeGomez`
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Feb 15, 2024
Rollup merge of rust-lang#111106 - Stargateur:doc/format_args, r=m-ou-se

Add known issue of let binding to format_args doc

Simply add doc about rust-lang#92698.

 `@rustbot` label +T-rustdoc -T-libs

 r? `@GuillaumeGomez`
@justinhaubrich
Copy link

So I ran into this issue, but at the same time I managed to find a rather nifty workaround. Basically it works if you use an anonymous closure and directly invoke it. (In my case I needed the length of the outputted string)

        (|buf : &mut fmt::Formatter, args : std::fmt::Arguments<'_>| {
            let i = args.to_string().len();
            buf.write_fmt(args)?;

            for _ in 0..(128 - i) {
                write!(buf, " ")?;
            }
            writeln!(buf, "{}:{}", file, line)
        })(buf, format_args!("[{}][{}] - {}", level, target, args))

For me this works and compiles on stable 1.61.

Thanks for this. Using a closure is the only way I was able to get it to work.

univerz referenced this issue in makepad/makepad Oct 9, 2024
@daniel-pfeiffer
Copy link

Since Rust hasn’t yet solved these problems, I’ve extended these solutions and published them.

@ArhanChaudhary
Copy link

Update: this will hopefully be fixed by #139080

bors added a commit to rust-lang-ci/rust that referenced this issue Mar 30, 2025
…try>

Simplify expansion for format_args!().

Instead of calling `Placeholder::new()`, we can just use a struct expression directly.

Before:

```rust
        Placeholder::new(…, …, …, …)
```

After:

```rust
        Placeholder {
                position: …,
                flags: …,
                width: …,
                precision: …,
        }
```

(I originally avoided the struct expression, because `Placeholder` had a lot of fields. But now that rust-lang#136974 is merged, it only has four fields left.)

This will make the `fmt` argument to `fmt::Arguments::new_v1_formatted()` a candidate for const promotion, which is important if we ever hope to fix rust-lang#92698 (It doesn't change anything yet though, because the `args` argument to `fmt::Arguments::new_v1_formatted()` is not const-promotable.)
@m-ou-se m-ou-se linked a pull request Mar 30, 2025 that will close this issue
@m-ou-se
Copy link
Member

m-ou-se commented Mar 30, 2025

Update: this will hopefully be fixed by #139080

Indeed!

Here's the solution for this issue: #139135

(Blocked on a few other PRs, and blocked on an FCP of course.)

bors added a commit to rust-lang-ci/rust that referenced this issue Mar 30, 2025
…ark-Simulacrum

Simplify expansion for format_args!().

Instead of calling `Placeholder::new()`, we can just use a struct expression directly.

Before:

```rust
        Placeholder::new(…, …, …, …)
```

After:

```rust
        Placeholder {
                position: …,
                flags: …,
                width: …,
                precision: …,
        }
```

(I originally avoided the struct expression, because `Placeholder` had a lot of fields. But now that rust-lang#136974 is merged, it only has four fields left.)

This will make the `fmt` argument to `fmt::Arguments::new_v1_formatted()` a candidate for const promotion, which is important if we ever hope to tackle rust-lang#92698 (It doesn't change anything yet though, because the `args` argument to `fmt::Arguments::new_v1_formatted()` is not const-promotable.)
github-actions bot pushed a commit to rust-lang/rustc-dev-guide that referenced this issue Mar 31, 2025
…acrum

Simplify expansion for format_args!().

Instead of calling `Placeholder::new()`, we can just use a struct expression directly.

Before:

```rust
        Placeholder::new(…, …, …, …)
```

After:

```rust
        Placeholder {
                position: …,
                flags: …,
                width: …,
                precision: …,
        }
```

(I originally avoided the struct expression, because `Placeholder` had a lot of fields. But now that rust-lang/rust#136974 is merged, it only has four fields left.)

This will make the `fmt` argument to `fmt::Arguments::new_v1_formatted()` a candidate for const promotion, which is important if we ever hope to tackle rust-lang/rust#92698 (It doesn't change anything yet though, because the `args` argument to `fmt::Arguments::new_v1_formatted()` is not const-promotable.)
bors added a commit to rust-lang-ci/rust that referenced this issue Mar 31, 2025
Remove 'simple array' lowering of format_args!().

format_args!() uses a simpler (faster to compile) lowering in simple cases. However, selecting that case is somewhat complicated, as it does not work if any argument beyond the first one contains a yield point, so we have to check for that.

As part of the solution for rust-lang#92698 and rust-lang#139136, it might need to get even more complicated, checking for any lifetime-extended temporaries or any const-promotable expressions.

This is an experiment to see the impact of just removing this optimazation.

This has been tried before with [slightly negative results](rust-lang#106770 (comment)), but maybe things have changed by now. :)
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this issue Apr 2, 2025
…ark-Simulacrum

Simplify expansion for format_args!().

Instead of calling `Placeholder::new()`, we can just use a struct expression directly.

Before:

```rust
        Placeholder::new(…, …, …, …)
```

After:

```rust
        Placeholder {
                position: …,
                flags: …,
                width: …,
                precision: …,
        }
```

(I originally avoided the struct expression, because `Placeholder` had a lot of fields. But now that rust-lang#136974 is merged, it only has four fields left.)

This will make the `fmt` argument to `fmt::Arguments::new_v1_formatted()` a candidate for const promotion, which is important if we ever hope to tackle rust-lang#92698 (It doesn't change anything yet though, because the `args` argument to `fmt::Arguments::new_v1_formatted()` is not const-promotable.)
m-ou-se added a commit to m-ou-se/rust that referenced this issue Apr 3, 2025
Experimental feature gate for `super let`

This adds an experimental feature gate, `#![feature(super_let)]`, for the `super let` experiment.

Tracking issue: rust-lang#139076

Liaison: `@nikomatsakis`

## Description

There's a rough (inaccurate) description here: https://blog.m-ou.se/super-let/

In short, `super let` allows you to define something that lives long enough to be borrowed by the tail expression of the block. For example:

```rust
let a = {
    super let b = temp();
    &b
};
```

Here, `b` is extended to live as long as `a`, similar to how in `let a = &temp();`, the temporary will be extended to live as long as `a`.

## Properties

During the temporary lifetimes work we did last year, we explored the properties of "super let" and concluded that the fundamental property should be that these two are always equivalent in any context:

1. `& $expr`
2. `{ super let a = & $expr; a }`

And, additionally, that these are equivalent in any context when `$expr` is a temporary (aka rvalue):

1. `& $expr`
2. `{ super let a = $expr; & a }`

This makes it possible to give a name to a temporary without affecting how temporary lifetimes work, such that a macro can transparently use a block in its expansion, without that having any effect on the outside.

## Implementing pin!() correctly

With `super let`, we can properly implement the `pin!()` macro without hacks: ✨

```rust
pub macro pin($value:expr $(,)?) {
    {
        super let mut pinned = $value;
        unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) }
    }
}
```

This is important, as there is currently no way to express it without hacks in Rust 2021 and before (see [hacky definition](https://github.com/rust-lang/rust/blob/2a06022951893fe5b5384f8dbd75b4e6e3b5cee0/library/core/src/pin.rs#L1947)), and no way to express it at all in Rust 2024 (see [issue](rust-lang#138718)).

## Fixing format_args!()

This will also allow us to express `format_args!()` in a way where one can assign the result to a variable, fixing a [long standing issue](rust-lang#92698):

```rust
let f = format_args!("Hello {name}!"); // error today, but accepted in the future! (after separate FCP)
```

## Experiment

The precise definition of `super let`, what happens for `super let x;` (without initializer), and whether to accept `super let _ = _ else { .. }` are still open questions, to be answered by the experiment.

Furthermore, once we have a more complete understanding of the feature, we might be able to come up with a better syntax. (Which could be just a different keywords, or an entirely different way of naming temporaries that doesn't involve a block and a (super) let statement.)
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Apr 3, 2025
Experimental feature gate for `super let`

This adds an experimental feature gate, `#![feature(super_let)]`, for the `super let` experiment.

Tracking issue: rust-lang#139076

Liaison: ``@nikomatsakis``

## Description

There's a rough (inaccurate) description here: https://blog.m-ou.se/super-let/

In short, `super let` allows you to define something that lives long enough to be borrowed by the tail expression of the block. For example:

```rust
let a = {
    super let b = temp();
    &b
};
```

Here, `b` is extended to live as long as `a`, similar to how in `let a = &temp();`, the temporary will be extended to live as long as `a`.

## Properties

During the temporary lifetimes work we did last year, we explored the properties of "super let" and concluded that the fundamental property should be that these two are always equivalent in any context:

1. `& $expr`
2. `{ super let a = & $expr; a }`

And, additionally, that these are equivalent in any context when `$expr` is a temporary (aka rvalue):

1. `& $expr`
2. `{ super let a = $expr; & a }`

This makes it possible to give a name to a temporary without affecting how temporary lifetimes work, such that a macro can transparently use a block in its expansion, without that having any effect on the outside.

## Implementing pin!() correctly

With `super let`, we can properly implement the `pin!()` macro without hacks: ✨

```rust
pub macro pin($value:expr $(,)?) {
    {
        super let mut pinned = $value;
        unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) }
    }
}
```

This is important, as there is currently no way to express it without hacks in Rust 2021 and before (see [hacky definition](https://github.com/rust-lang/rust/blob/2a06022951893fe5b5384f8dbd75b4e6e3b5cee0/library/core/src/pin.rs#L1947)), and no way to express it at all in Rust 2024 (see [issue](rust-lang#138718)).

## Fixing format_args!()

This will also allow us to express `format_args!()` in a way where one can assign the result to a variable, fixing a [long standing issue](rust-lang#92698):

```rust
let f = format_args!("Hello {name}!"); // error today, but accepted in the future! (after separate FCP)
```

## Experiment

The precise definition of `super let`, what happens for `super let x;` (without initializer), and whether to accept `super let _ = _ else { .. }` are still open questions, to be answered by the experiment.

Furthermore, once we have a more complete understanding of the feature, we might be able to come up with a better syntax. (Which could be just a different keywords, or an entirely different way of naming temporaries that doesn't involve a block and a (super) let statement.)
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Apr 3, 2025
Rollup merge of rust-lang#139080 - m-ou-se:super-let-gate, r=traviscross

Experimental feature gate for `super let`

This adds an experimental feature gate, `#![feature(super_let)]`, for the `super let` experiment.

Tracking issue: rust-lang#139076

Liaison: ``@nikomatsakis``

## Description

There's a rough (inaccurate) description here: https://blog.m-ou.se/super-let/

In short, `super let` allows you to define something that lives long enough to be borrowed by the tail expression of the block. For example:

```rust
let a = {
    super let b = temp();
    &b
};
```

Here, `b` is extended to live as long as `a`, similar to how in `let a = &temp();`, the temporary will be extended to live as long as `a`.

## Properties

During the temporary lifetimes work we did last year, we explored the properties of "super let" and concluded that the fundamental property should be that these two are always equivalent in any context:

1. `& $expr`
2. `{ super let a = & $expr; a }`

And, additionally, that these are equivalent in any context when `$expr` is a temporary (aka rvalue):

1. `& $expr`
2. `{ super let a = $expr; & a }`

This makes it possible to give a name to a temporary without affecting how temporary lifetimes work, such that a macro can transparently use a block in its expansion, without that having any effect on the outside.

## Implementing pin!() correctly

With `super let`, we can properly implement the `pin!()` macro without hacks: ✨

```rust
pub macro pin($value:expr $(,)?) {
    {
        super let mut pinned = $value;
        unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) }
    }
}
```

This is important, as there is currently no way to express it without hacks in Rust 2021 and before (see [hacky definition](https://github.com/rust-lang/rust/blob/2a06022951893fe5b5384f8dbd75b4e6e3b5cee0/library/core/src/pin.rs#L1947)), and no way to express it at all in Rust 2024 (see [issue](rust-lang#138718)).

## Fixing format_args!()

This will also allow us to express `format_args!()` in a way where one can assign the result to a variable, fixing a [long standing issue](rust-lang#92698):

```rust
let f = format_args!("Hello {name}!"); // error today, but accepted in the future! (after separate FCP)
```

## Experiment

The precise definition of `super let`, what happens for `super let x;` (without initializer), and whether to accept `super let _ = _ else { .. }` are still open questions, to be answered by the experiment.

Furthermore, once we have a more complete understanding of the feature, we might be able to come up with a better syntax. (Which could be just a different keywords, or an entirely different way of naming temporaries that doesn't involve a block and a (super) let statement.)
github-actions bot pushed a commit to rust-lang/rustc-dev-guide that referenced this issue Apr 7, 2025
Experimental feature gate for `super let`

This adds an experimental feature gate, `#![feature(super_let)]`, for the `super let` experiment.

Tracking issue: rust-lang/rust#139076

Liaison: ``@nikomatsakis``

## Description

There's a rough (inaccurate) description here: https://blog.m-ou.se/super-let/

In short, `super let` allows you to define something that lives long enough to be borrowed by the tail expression of the block. For example:

```rust
let a = {
    super let b = temp();
    &b
};
```

Here, `b` is extended to live as long as `a`, similar to how in `let a = &temp();`, the temporary will be extended to live as long as `a`.

## Properties

During the temporary lifetimes work we did last year, we explored the properties of "super let" and concluded that the fundamental property should be that these two are always equivalent in any context:

1. `& $expr`
2. `{ super let a = & $expr; a }`

And, additionally, that these are equivalent in any context when `$expr` is a temporary (aka rvalue):

1. `& $expr`
2. `{ super let a = $expr; & a }`

This makes it possible to give a name to a temporary without affecting how temporary lifetimes work, such that a macro can transparently use a block in its expansion, without that having any effect on the outside.

## Implementing pin!() correctly

With `super let`, we can properly implement the `pin!()` macro without hacks: ✨

```rust
pub macro pin($value:expr $(,)?) {
    {
        super let mut pinned = $value;
        unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) }
    }
}
```

This is important, as there is currently no way to express it without hacks in Rust 2021 and before (see [hacky definition](https://github.com/rust-lang/rust/blob/2a06022951893fe5b5384f8dbd75b4e6e3b5cee0/library/core/src/pin.rs#L1947)), and no way to express it at all in Rust 2024 (see [issue](rust-lang/rust#138718)).

## Fixing format_args!()

This will also allow us to express `format_args!()` in a way where one can assign the result to a variable, fixing a [long standing issue](rust-lang/rust#92698):

```rust
let f = format_args!("Hello {name}!"); // error today, but accepted in the future! (after separate FCP)
```

## Experiment

The precise definition of `super let`, what happens for `super let x;` (without initializer), and whether to accept `super let _ = _ else { .. }` are still open questions, to be answered by the experiment.

Furthermore, once we have a more complete understanding of the feature, we might be able to come up with a better syntax. (Which could be just a different keywords, or an entirely different way of naming temporaries that doesn't involve a block and a (super) let statement.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one.
Projects
None yet
Development

Successfully merging a pull request may close this issue.