RFC: Super Parameters
Summary
This RFC introduces super parameters, a mechanism to temporarily take ownership of a parameter and return it to the caller while creating borrows tied to its lifetime. This enables functions to:
- Mutate owned values and return borrows without blocking subsequent immutable borrows.
- Express precise FFI interactions (e.g., with garbage-collected systems).
- Lay groundwork for future
super let
patterns.
Motivation
Rust's ownership system prevents common patterns where a function needs to:
- Return a borrow derived from a mutable reference (e.g.,
get_or_insert
). - Temporarily transfer ownership for external interactions (e.g., FFI with GC).
- Reassign values while keeping them valid for the caller.
Example: Today, HashMap::get_or_insert
returns &T
but blocks other immutable borrows due to the retained &mut self
. With super parameters:
impl HashMap<K, V> {
pub fn get_or_insert<'a>(super 'a mut self, key: K) -> &'a V {
// Mutate `self` and return a borrow without blocking future borrows
}
}
Guide-level Explanation
Basic Syntax
A super parameter is declared with super 'lt param: T
, indicating:
- The function takes ownership of
param
. - At function exit,
param
is returned to the caller, and any borrows from it must not exceed'lt
.
fn process<'a>(super 'a data: Data) -> &'a ProcessedData {
// Use `data` mutably, then return a borrow
&data.process()
}
Method Sugar
For methods, super 'a self
is syntactic sugar for super 'a self: Self
, not for self: super 'a Self
(something similars to mut self
rather than &mut self
):
impl Widget {
pub fn configure<'a>(super 'a self) -> &'a Self {
// Reconfigure and return a reference
}
}
Restrictions
- No implicit moves: Moving a super parameter is forbidden unless the function is marked as
super fn
(see below).
super fn
Functions
A super fn
guarantees that panics will propagate to the caller without being caught (via catch_unwind
), allowing moves:
// This super means, when panic, all parameter marked `super` must be drop if it's not being either moved or dropped, while this panic cannot be caught by its caller.
// For its caller, since it knows all super parameter are either dropped or moved by the super fn, the caller's panic will not either trigger double-free or unexpected memory leak.
super fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
if random_bool() {
drop(foo);
panic!("fine.");
} else {
foo = Foo::new();
}
return &foo
}
Reference-level Explanation
Type System Changes
- Ownership Transfer: Super parameters are owned by the function but must be valid at return.
- Lifetime Binding: The lifetime
'lt
insuper 'lt
is tied to the returned borrows. - Borrow Checker Rules:
- During the function body, super parameters can be borrowed mutably or immutably.
- All borrows derived from super parameters must not outlive
'lt
. - At function exit, the super parameter must be fully initialized (if moved).
Code Generation
The compiler ensures:
- Super parameters are not dropped (ownership is returned to the caller).
- Borrows from super parameters respect
'lt
.
FFI Example
Interacting with a garbage-collected system:
struct RContext;
impl RContext {
pub fn alloc<'a>(super 'a self) -> GcRef<'a> {
// Allocate an object that may be GC'd unless protected
}
pub fn protect<'a>(&self, obj: GcRef<'a>) -> ProtectedRef {
// Safeguard from GC
}
pub fn get_status(&self) -> RStatus {
// Get some status without triggering GC.
}
}
Drawbacks
- Complexity: Adds new syntax and lifetime semantics.
- Learning Curve: Developers must understand when to use super parameters vs references.
- Panic Safety: Requires careful handling in
super fn
functions.
Rationale and Alternatives
Alternatives
- Macros: Use macros to simulate ownership return, but they lack type-system integration.
Why Super Parameters?
- Precision: Enables patterns impossible with current borrow rules.
- FFI Safety: Provides lifetimes for resources managed externally (e.g., GC).
Prior Art
- Rust RFC 2094 (Non-lexical lifetimes): Lays groundwork for finer-grained borrow analysis.
- Swift
inout
Parameters: Modify passed values while retaining caller ownership.
Unresolved Questions
- Closures: Should
FnSuper
traits be introduced, or can super parameters work with existingFn
/FnMut
? - Syntax Sugar: Can we infer lifetimes in simple cases (e.g.,
fn foo(super param: T) -> &T
)? - Move Semantics: How to safely allow moves in non-
super fn
contexts?
Future Possibilities
- Super Let: Combine with
super let
to reassign variables conditionally. - Panic Handlers: Extend
super fn
to allow custom drop logic during panics. - Trait Integration: Allow traits to specify super parameter semantics.
Acknowledge
This is a Pre-RFC for Super borrow, which is firstly generated by DeepSeek R1 with very informal Chinese prompt as follows, and I correct some misunderstandings.
Chinese prompt, in case you're really interested in:
请尝试用英语写一份Rust的RFC,使得Rust可以支持一个名为super parameter的特性。 RFC应包含Summary, Motivation, Guide-level explanation, Reference-level explanation, Drawbacks, Rationale and alternatives, Prior art以及Unresolved questions RFC的主要内容可以参考https://internals.rust-lang.org/t/discussion-super-borrow-for-super-let-placement-new-and-hybrid-borrow/22207,但考虑到RFC是这个discussion演化的结果,如果discussion中出现的内容与这里的内容矛盾,应尽量以这里的内容为主。但如果这个RFC中有错漏或者更好的实现,请直接修正。这个名为super parameter的特性将支持Rust函数在接收一个在变量(称为super parameter)的所有权之后,在函数结束时将super parameter所有权返还调用者,借此允许Rust程序创建可以返还给调用者的临时借用 这个特性需要分别标记变量为super parameter,且显式标记返回变量时,super parameter的被借用状况。以下示例以super 'a和super 'a mut将函数标记为super parameter及其生命周期。
// 这里,`'a`的意思是,在函数返回时foo被借用,借用长度为'a,且foo在返回时至多被借用了'a
// 在这种语法下,函数可以获取foo的大部分所有权:可以在满足借用规则的情况下任意使用&mut foo与&foo。当且仅当foo为Copy时可以使用foo本身,
fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
Foo::modify(&mut foo); // 由于以及获得了foo的所有权,所以可以生成对foo的可变借用
// drop(foo); // 我们需要在后文讨论是否允许move这个数值。我倾向于不允许无效化foo,因为无效化foo之后,如果在对foo重新赋值之前函数触发了panic,那么这个函数及其调用者都会受到影响。因此我们没办法捕捉这个函数产生的错误
foo = Foo::new(); // 由于获得了所有权,我们可以直接用一个新的有效值代替原始数值
return &foo;
}
// 有关self的语法糖:
impl Bar {
pub fn sugar<'a>(super 'a self) -> &'a Self {todo!()}
// 需注意,super 'a self的意思不是self: super 'a Self,而是像下面展示的那样
pub fn full_signature<'a>(super 'a self: Self) -> &'a Self {todo!()}
// 如果函数接收的不是self,可以这样标记:
pub fn in_case_sugar_is_not_desired<'a>(super 'a self: Box<Self>) -> &'a Self {todo!()}
}
这个新特性的应用场景包括:
- 允许可变借用之后返回不可变借用,且不影响生成其他不可变借用: 将
pub fn get_or_insert(&mut self, value: T) -> &T
这种会影响继续不可变借用self的函数改成pub fn get_or_insert<'a>(mut 'a self, value: T) -> &'a T
这种不影响继续不可变借用self的 - 允许写出更精细的FFI调用系统。比如,当我们需要与某个可能会GC的语言(比如R语言)进行交互时,我们可以将R语言的所有交互抽象为一个ZST类型,在调用R的FFI接口生成未被保护可能被GC的数值时,将返回数值的生命周期标记为'a。此时我们仍然可以调用不会触发GC的FFI接口,但不能继续调用那些会触发GC的接口:
struct RFfi;
impl RFfi {
pub fn alloc_variable<'a>(super 'a self) -> Unprotected<'a> {todo!("calling unsafe FFI interface here.")}
pub fn manual_gc(&mut self) {todo!("calling unsafe FFI interface here.")}
pub fn protect<'a>(&self, val: Unprotected<'a>) -> Protected {todo!("calling unsafe FFI interface here.")}
}
- 与super let结合,提供另一种实现super let的可能:未来或许可以允许使用默认值以模拟super let
关于是否允许对super parameter执行move语义:
显然这段可能会panic的程序不能以任何形式被包在pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R>
中,因为按函数签名,foo将会返回一个有效的foo(生命周期至少有'a),但函数panic时,foo的状态并非有效的。
fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
if random_bool() {
drop(foo);
panic!("fine.");
} else {
foo = Foo::new();
}
return &foo
}
或许我们可以加入一个panic-free标记,由编译器保证函数会处理全部panic并正常返回,或者加入一个修饰函数的super符号,表示这个函数并不是UnwindSafe的,以此保证函数的panic不会被其调用者拦截,因而可以借此触发函数的panic。但考虑到pub super fn
容易和pub(super) fn
混淆,我们或许可以暂时不实现move super parameter的方法直到找到一个合适的标记。
// super fn的意思是,这个函数的panic必然会令其调用者panic
// 当super fn发生panic时,super fn应当负责drop全部还未drop的super parameter,并令调用者进入panic状态
// 如果调用者也是suoer fn,由于我们约定了super parameter的处理者是被调用的super fn,所以调用者不必(也不应该)继续drop这些super parameter
// 因此,在panic被最终处理时,所有可能无效的super parameter都会且只会被drop一次,且函数的错误处理必然停在某个不是super fn的位置。也由此,在panic处理结束时,余下的全部变量仍然都是有效的。
super fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
drop(foo);
panic!("It's fine to panic, since the caller who owns foo is panic, too.");
}
super parameter与闭包: 我实在没想好是否要新增一个FnSuper<'a>,还是将FnSu与FnMut合并。
一些语法糖:
- 省略super: 尽管按照Rust当前语法,省略super不会造成任何歧义,但考虑到foo('a self)或许会与foo(&'a self)混淆。在不引起歧义的前提下,或许我们可以引入
fn foo<'a>('a foo: Foo)
这种语法作为语法糖来减小函数签名长度。 - 省略生命周期: 当super借用只有一个,且返回只存在一个生命周期时,生命周期可以省略:
fn foo(super self) -> &Self {todo!()}
fn bar(super bar: Bar) -> &Bar {todo!()}
// 下面这种情况,或许不应该自动推理:
// fn baz<'a>(super bar: Baz<'a>) -> &Baz<'a> {todo!()}
Original Result generated by DeepSeek R1
# RFC: Super Parameters- Feature Name:
super_parameters
- Start Date: 2023-10-01
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
Summary
This RFC introduces super parameters, a mechanism to temporarily take ownership of a parameter and return it to the caller while creating borrows tied to its lifetime. This enables functions to:
- Mutate owned values and return borrows without blocking subsequent immutable borrows.
- Express precise FFI interactions (e.g., with garbage-collected systems).
- Lay groundwork for future
super let
patterns.
Motivation
Rust's ownership system prevents common patterns where a function needs to:
- Return a borrow derived from a mutable reference (e.g.,
get_or_insert
). - Temporarily transfer ownership for external interactions (e.g., FFI with GC).
- Reassign values while keeping them valid for the caller.
Example: Today, HashMap::get_or_insert
returns &T
but blocks other immutable borrows due to the retained &mut self
. With super parameters:
impl HashMap<K, V> {
pub fn get_or_insert<'a>(super 'a mut self, key: K) -> &'a V {
// Mutate `self` and return a borrow without blocking future borrows
}
}
Guide-level Explanation
Basic Syntax
A super parameter is declared with super 'lt param: T
, indicating:
- The function takes ownership of
param
. - At function exit,
param
is returned to the caller, and any borrows from it must not exceed'lt
.
fn process<'a>(super 'a data: Data) -> &'a ProcessedData {
// Use `data` mutably, then return a borrow
&data.process()
}
Method Sugar
For methods, super 'a self
is syntactic sugar for self: super 'a Self
:
impl Widget {
pub fn configure<'a>(super 'a mut self) -> &'a Self {
// Reconfigure and return a reference
}
}
Restrictions
- No implicit moves: Moving a super parameter is forbidden unless the function is marked
super fn
(see below). - Panic safety: Functions not marked
super fn
must ensure all super parameters are valid on panic.
super fn
Functions
A super fn
guarantees that panics will propagate to the caller without being caught (via catch_unwind
), allowing moves:
super fn risky_operation<'a>(super 'a data: Data) -> &'a Data {
let new_data = generate_data();
data = new_data; // Allowed because panics will not be caught
&data
}
Reference-level Explanation
Type System Changes
- Ownership Transfer: Super parameters are owned by the function but must be valid at return.
- Lifetime Binding: The lifetime
'lt
insuper 'lt
is tied to the returned borrows. - Borrow Checker Rules:
- During the function body, super parameters can be borrowed mutably or immutably.
- All borrows derived from super parameters must not outlive
'lt
. - At function exit, the super parameter must be fully initialized (if moved).
Code Generation
The compiler ensures:
- Super parameters are not dropped (ownership is returned to the caller).
- Borrows from super parameters respect
'lt
.
FFI Example
Interacting with a garbage-collected system:
struct RContext;
impl RContext {
pub fn alloc<'a>(super 'a self) -> GcRef<'a> {
// Allocate an object that may be GC'd unless protected
}
pub fn protect<'a>(&self, obj: GcRef<'a>) -> ProtectedRef {
// Safeguard from GC
}
}
Drawbacks
- Complexity: Adds new syntax and lifetime semantics.
- Learning Curve: Developers must understand when to use super parameters vs references.
- Panic Safety: Requires careful handling in
super fn
functions.
Rationale and Alternatives
Alternatives
- Explicit Reassignment: Require manual reassignment (e.g.,
param = T::new()
), but this is error-prone. - Macros: Use macros to simulate ownership return, but they lack type-system integration.
Why Super Parameters?
- Precision: Enables patterns impossible with current borrow rules.
- FFI Safety: Provides lifetimes for resources managed externally (e.g., GC).
Prior Art
- C++ Reference Parameters: Similar to taking a reference but with explicit lifetime control.
- Rust RFC 2094 (Non-lexical lifetimes): Lays groundwork for finer-grained borrow analysis.
- Swift
inout
Parameters: Modify passed values while retaining caller ownership.
Unresolved Questions
- Closures: Should
FnSuper
traits be introduced, or can super parameters work with existingFn
/FnMut
? - Syntax Sugar: Can we infer lifetimes in simple cases (e.g.,
fn foo(super param: T) -> &T
)? - Move Semantics: How to safely allow moves in non-
super fn
contexts?
Future Possibilities
- Super Let: Combine with
super let
to reassign variables conditionally. - Panic Handlers: Extend
super fn
to allow custom drop logic during panics. - Trait Integration: Allow traits to specify super parameter semantics.
```markdown
, Since the forum cannot render markdown, I deleted its first line and last line.)