I refuse to read an AI-generated pre-RFC, even if it’s just a pre-RFC, and even though I do appreciate your transparency on the process.
Please also note that our forum TOS has a rule against machine-generated content being posted, and we tend to use that rule to say AI-generated content isn’t accepted, at least over on users.rust-lang.org
Like, why do I have to read this nonesense, for instance?
The inout
prior art of Swift is usually compared to to normal &mut T
references in Rust, arguably. I’m not aware of any aspect of those that makes them more similar to this feature here, which seems pretty Rust specific and about lifetimes and/or about handling of truly uninitialized values during panics. Why calling out NLL should be of any relevance, isn’t clear at all.
There’s no point in reviewing a text in this form, nor calling it a Pre-RFC, if it’s still containing unreviewed AI hallucination.
This is also a major (an obvious) drawback: i.e. it’s re-creating a second (or maybe 3rd, if you count fn(T) -> T
-style APIs) way of doing “inout” with vastly different syntax, comparatively little difference for many practical use-cases.
I have instead translated your prompt and will be responding mostly only to what I can read in there.
translated prompt (click to expand)
Please try to write an RFC in English that allows Rust to support a feature called super parameter.
The RFC should include the following sections: Summary, Motivation, Guide-level explanation, Reference-level explanation, Drawbacks, Rationale and alternatives, Prior art, and Unresolved questions.
The main content of the RFC can refer to https://internals.rust-lang.org/t/discussion-super-borrow-for-super-let-placement-new-and-hybrid-borrow/22207. However, since the RFC is considered an evolution of that discussion, if there is any conflict between the discussion and what is stated here, please give priority to what is stated here. If there are any errors or better implementations in this RFC, please directly correct them.
This feature, called super parameter, will allow a Rust function that receives ownership of a variable (called a super parameter) to return the ownership of that super parameter back to the caller when the function ends. By doing so, Rust programs can create temporary borrows that can be returned to the caller.
This feature requires marking variables as super parameters, and explicitly marking the borrowing state of the super parameter when returning the variable. In the examples below, super 'a
and super 'a mut
are used to mark the function as having a super parameter with a certain lifetime.
// Here, `'a` means that when the function returns, foo is borrowed with lifetime 'a,
// and at most borrowed for 'a. In this syntax, the function can receive most of foo’s
// ownership: it can freely use &mut foo and &foo under normal borrowing rules.
// If and only if foo is Copy, it can use foo itself.
fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
Foo::modify(&mut foo);
// Since ownership of foo has been taken, we can create a mutable borrow of foo.
// drop(foo);
// We need to discuss whether moving this value is allowed.
// I tend to disallow invalidating foo because if foo is dropped here,
// and if the function panics before reassigning foo, then both the function
// and its caller would be affected. We can’t safely handle errors arising in this manner.
foo = Foo::new();
// Because we have ownership, we can directly replace the original value with a new valid value.
return &foo;
}
// Regarding the syntax sugar for self:
impl Bar {
pub fn sugar<'a>(super 'a self) -> &'a Self {
todo!()
}
// Note that super 'a self does not mean self: super 'a Self,
// but rather as shown below:
pub fn full_signature<'a>(super 'a self: Self) -> &'a Self {
todo!()
}
// If the function does not take self, you can mark it like this:
pub fn in_case_sugar_is_not_desired<'a>(super 'a self: Box<Self>) -> &'a Self {
todo!()
}
}
Possible use cases of this new feature include:
-
Allowing a mutable borrow to return an immutable borrow without affecting further immutable borrows:
For example, changingpub fn get_or_insert(&mut self, value: T) -> &T
—which affects subsequent immutable borrows ofself
—intopub fn get_or_insert<'a>(mut 'a self, value: T) -> &'a T
, which does not interfere with continuing to borrowself
immutably. -
Enabling a more fine-grained FFI call system.
For instance, when interacting with a garbage-collected language (like R), we can encapsulate all interactions with a zero-sized type (ZST). When calling R’s FFI interface to generate an unprotected value that might be garbage-collected, we mark the returned value’s lifetime as'a
. We can still call FFI interfaces that do not trigger GC, but cannot call those that may trigger 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.") } }
-
Integrating with super let, providing another way to implement super let by possibly allowing the use of default values in the future to simulate it.
On whether to allow move semantics for super parameters:
Clearly, the following snippet, which may panic, cannot be safely wrapped in something like pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R>
because, according to the function signature, foo
must be returned valid (with at least lifetime 'a
). However, if the function panics, foo
’s validity is not guaranteed:
fn foo<'a>(super 'a foo: Foo) -> &'a Foo {
if random_bool() {
drop(foo);
panic!("fine.");
} else {
foo = Foo::new();
}
return &foo;
}
One idea is introducing a panic-free marker that guarantees the function handles all panics and returns normally, or another marker (e.g., super fn) to indicate the function is not UnwindSafe
. In that case, if the function panics, the caller cannot intercept it. However, because pub super fn
could be confused with pub(super) fn
, we might delay implementing move semantics for super parameters until a more appropriate marker is found.
// super fn means that if this function panics, the caller also panics.
// When super fn panics, it is responsible for dropping all super parameters
// that haven’t been dropped yet, and ensures the caller also enters a panic state.
// If the caller is also a super fn, it does not drop these parameters again.
// By the time panic handling finishes, any super parameters that might be invalid
// will have been dropped exactly once, and the error handling will stop at a non-super fn.
// Consequently, all remaining variables are still valid.
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 panicking too.");
}
Super parameters and closures:
It’s uncertain whether a new trait such as FnSuper<'a>
is needed or whether it should be combined with FnMut
. More discussion is required on this point.
Some syntactic sugar:
-
Omitting
super
:
Although under current Rust syntax omittingsuper
might not cause ambiguity, it could be confusing to seefoo('a self)
vs.foo(&'a self)
. Where there’s no confusion, we could introduce something like:fn foo<'a>('a foo: Foo)
as a shorthand to reduce function signature length.
-
Omitting lifetimes:
If there is only one super borrow and only one return lifetime, you can omit the lifetime:fn foo(super self) -> &Self { todo!() } fn bar(super bar: Bar) -> &Bar { todo!() } // For a case like below, automatic inference might not be appropriate: // fn baz<'a>(super bar: Baz<'a>) -> &Baz<'a> { todo!() }
From what I read in this text, this feature covers two separate ideas I’ve seen discussed before:
- a way to downgrade a borrow, first mutable, then immutable for longer
- IIRC I’ve myself come across this thought e.g. in the larger picture of view types, e.g. you can declare some
&{field_name} Type
reference that only partially borrows, just the field; and this could naturally extend to choosing different mutability and different lifetimes for fields, e.g.&{'a field_1, 'b mut field_2} Type
, which is only one step removed from giving the same field different lifetimes for mutable & immutable - on its own, such a generalization of
&mut T
references might be as simple as introducing a second lifetime, e.g.&'a mut<'b> T
where'a: 'b
and'b
explains the time frame in which the reference is mutable; this could be an extension to existing&'a mut T
by it desugaring to&'a mut<'a> T
; - for more discussion, see e.g. this topic: Pre-RFC: Downgradable mutable borrows
as well as other topics that it links to, and topics that it’s linked from
- IIRC I’ve myself come across this thought e.g. in the larger picture of view types, e.g. you can declare some
- a way (marked partially optional - i.e. your “super fn”?) to transfer/take ownership out of a reference (while avoiding the usual issues around panic safety)
- this comes up sometimes as certain possible flavors of
&move
/&own
-style references, e.g. here’s a thread: Pre-RFC: Move references - this is also sometimes discussed as a possible feature of normal mutable references in a
panic=abort
configuration, e.g.: Allow moving out of mutable references with `panic=abort` - you superficially relate this feature to
super let
, howeversuper let
-like functionality, explicized into explicit parameters to determine the place the value goes would rather need some sort of&out
-style reference; which in turn can probably already be somewhat modeled anyway, be it with APIs like the ones discussed in the&move
-style references thread linked above, or something as simple as a&'a mut Option<T>
which is to be called with aNone
value. And none of all of these approaches (the&out
variable; the APIs in the other thread, or&'mut Option<…>
or yoursuper 'a self
) actually prohibit
eventual direct & owned access to the value in question;
or the choice of where else (besides a stack frame) it might live,
so this doesn’t actually fully meet existingfn
-levelsuper let
discussions which were concerned with thepin!
macro for an example. Sound pinning can allow neither: no eventual direct owned access to the value in question, nor the choice to have it stored somewhere else than directly on a stack frame (or anasync
block’s stack, which is properly pinned with drop guarantees, too)
- this comes up sometimes as certain possible flavors of
Other than these ideas (&'a mut<'b> T
or &move
-flavored reference types), your proposal doesn’t introduce any new types though. You seem to instead be proposing a new style of function parameter that can’t be expressed in types. Not a first-class citizen. Maybe there’s good reason why it’s easier this way, but it would IMO feel a lot more “Rusty” to try to express such access through a type instead of through new magic around functions – function coloring with const
or async
is already much complexity, a new parameter type also seems nicer (if possible) in this regard.
-
your explanations aren’t very thorough on the limitations where and how exactly this style of function can be called, as far as I can tell. For instance, you seem to be providing not a single example where such a function is actually called. It sounds like it requires the caller to own the value… should clarify these things more: e.g. can you pass on a
super
parameter to anothersuper
-parameter-expecting function? etc… -
the explanation on semantics of:
a Rust function that receives ownership of a variable (called a super parameter) to return the ownership of that super parameter back to the caller when the function ends
are a bit questionable. If you create a borrow within the function body and then literally return the ownership (i.e. a move?), that would kill the borrow
-
what is it about the use or manual
return
statements; is that somehow a requirement? Does the borrow always happen in thereturn
? Or is this just coincidence and unidiomatic style?