{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreicadwf3zimesvchdfa57mo6pvmuv7mvactonbg3o3jp3e4zj552ue",
"uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mk5yiucgf3c2"
},
"path": "/t/discussion-a-perspective-on-super-let-could-a-related-lift-capability-ever-belong-in-rust-s-macro-system/24193#post_1",
"publishedAt": "2026-04-23T12:05:11.000Z",
"site": "https://internals.rust-lang.org",
"textContent": "Hi everyone,\n\nAs 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.\n\n#### 1. The Core Observation: super let is essentially a hygienic AST Lift\n\nIf we look strictly at the Borrow Checker rules, super let doesn't actually introduce new lifetime semantics. It essentially performs two tasks:\n\n 1. **Relocation (Drop Scope)** : It hoists the variable declaration to the outer scope to extend its extent/survival time.\n\n 2. **Hygiene (Lexical Scope)** : It locks the visibility of the variable strictly to the inner block, preventing external access.\n\n\n\n\nIn other words, super let is a mechanism to \"hygienically leak a variable's drop scope to the outside without leaking its lexical visibility.\"\n\nThis 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.\n\n#### 2. The Racket Inspiration: syntax-local-lift-expression\n\nIn 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).\n\nWhen 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.\n\n#### 3. What if Rust Macros had lift capabilities?\n\nIf 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.\"\n\n**Potential advantages of this approach:**\n\n * **More Powerful Metaprogramming** : Macros could conditionally lift various constructs, offering developers more flexibility without being limited to let bindings.\n\n * **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.\n\n\n\n\n#### 4. The Trade-offs\n\nI understand that Rust and Racket have fundamentally different architectures. I can see a few reasons why super let might be preferred over macro lift:\n\n 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.\n\n 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.\n\n 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.\n\n\n\n\n#### 5. Summary and Questions\n\nMy 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.\n\nThat 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.\n\nSo I’d be very interested to hear the lang/compiler team’s thoughts on a few questions:\n\n 1. Was a macro-level “lift” capability ever considered during the early design discussions around this space?\n 2. Is there a fundamental reason this kind of outward placement should remain a core-language feature rather than something macros could also express?\n 3. Even if `super let` is the right design for Rust, could a related lift-like capability ever make sense for future macro evolution?\n\n\n\nLooking forward to hearing your thoughts!",
"title": "[Discussion] A perspective on super let: could a related “lift” capability ever belong in Rust’s macro system?"
}