External Publication
Visit Post

[Discussion] A perspective on super let: could a related “lift” capability ever belong in Rust’s macro system?

Rust Internals [Unofficial] April 23, 2026
Source

Hi everyone,

As I’ve been closely following the exciting progress of #139076 (super let), I want to share a perspective inspired by the macro systems of Lisp/Racket. While super let beautifully solves the temporary lifetime and hygiene issues, I’ve been wondering if this capability could—or should—be generalized as a feature of the Rust macro system itself.

1. The Core Observation: super let is essentially a hygienic AST Lift

If we look strictly at the Borrow Checker rules, super let doesn't actually introduce new lifetime semantics. It essentially performs two tasks:

  1. Relocation (Drop Scope) : It hoists the variable declaration to the outer scope to extend its extent/survival time.

  2. Hygiene (Lexical Scope) : It locks the visibility of the variable strictly to the inner block, preventing external access.

In other words, super let is a mechanism to "hygienically leak a variable's drop scope to the outside without leaking its lexical visibility."

This is also why it seems closely related to temporary lifetime extension : in that case, the compiler already performs a limited, implicit version of this idea for certain syntactic forms, effectively placing an intermediate temporary in a scope where the resulting borrow can remain valid. From this perspective, super let looks like a way to make that kind of placement explicit and programmable, while preserving hygiene.

2. The Racket Inspiration: syntax-local-lift-expression

In the Racket/Scheme macro ecosystem, this exact problem is solved without introducing core language keywords. Instead, the macro system is empowered with a lift capability (e.g., syntax-local-lift-expression).

When a macro in Racket needs to generate an expression that should be evaluated/stored in an outer context (while keeping its identifier perfectly hygienic locally), the macro "lifts" it. The macro expander physically moves the declaration to the outer scope, but leaves behind a hygienic identifier for the local code to use.

3. What if Rust Macros had lift capabilities?

If Rust's macro system (e.g., Proc-macros) were extended to support lift, macros could natively solve the temporary lifetime issues. For example, a macro like pin!() or format_args!() could use a hypothetical TokenStream::lift() API. The macro would tell the compiler: "Please place this generated temporary variable in the parent block's scope, but only give me back the hygienic identifier to use in this local expansion."

Potential advantages of this approach:

  • More Powerful Metaprogramming : Macros could conditionally lift various constructs, offering developers more flexibility without being limited to let bindings.

  • Keeping Core Language Smaller : It keeps the complexity inside the metaprogramming domain rather than adding a new feature (super) to the core language. After all, the temporary lifetime issue is mostly felt when authoring or using macros. In ordinary day-to-day code, I think this issue is often not catastrophic.

4. The Trade-offs

I understand that Rust and Racket have fundamentally different architectures. I can see a few reasons why super let might be preferred over macro lift:

  1. In-place Expansion : Rust macros currently operate on strict in-place token replacement. Modifying the AST outside the macro's invocation span would require a massive architectural shift in the compiler's expansion engine.

  2. Everyday Ergonomics : super let isn't just for macro authors. It dramatically improves the ergonomics for regular developers facing "delayed initialization" patterns in daily coding.

  3. Explicitness : Rust values explicit control flow. super let makes the extend ed drop scope visibly explicit to the reader, whereas a macro lift would hide this side-effect.

5. Summary and Questions

My current intuition is that super let is best understood not as “lifetime magic,” but as an explicit, hygienic variable-placement mechanism. In that sense, it feels closely connected to temporary lifetime extension, except that it turns an implicit and syntax-restricted behavior into something explicit and controllable.

That in turn makes me wonder whether super let might be viewed as one specialized language construct for a more general capability: hygienically placing a generated binding in an outer drop scope while keeping its lexical visibility local.

So I’d be very interested to hear the lang/compiler team’s thoughts on a few questions:

  1. Was a macro-level “lift” capability ever considered during the early design discussions around this space?
  2. Is there a fundamental reason this kind of outward placement should remain a core-language feature rather than something macros could also express?
  3. Even if super let is the right design for Rust, could a related lift-like capability ever make sense for future macro evolution?

Looking forward to hearing your thoughts!

Discussion in the ATmosphere

Loading comments...