The more I think about this, the more I feel like "return references" are the right answer here.
We currently have the following matrix:
read-only | read-write | write-only | |
---|---|---|---|
owned | let foo = bar; |
let mut foo = bar; |
let foo; |
reference | &foo |
&mut foo |
??? |
It feels like what we're trying to do in this thread is fill in the last cell in that matrix. I'll strawman this as &write
(or perhaps &uninit
).
Concretely, this code works fine because the compiler can do enough analysis to verify that v
is initialised before any possible reads:
fn main() {
let mut r = "blah";
let v;
if r.len() % 2 == 1 {
v = r.to_uppercase();
r = &v;
}
println!("{}", r);
}
But there's currently no formalisation for saying that a reference can be uninitialised, and performing this analysis.
MaybeUninit
is working along the same lines, but feels like a it's catering towards a very different kind of analysis. MaybeUninit
is saying "Here's some memory, the caller will manage its initialisation out of band" (hence its unsafety).
&write
is saying "The compiler can already do initialisation analysis, I'd like it to expand the scope of where it does that analysis".
You can currently write:
fn main() {
let mut v = String::new();
println!("{}", upper_if_odd("blah", &mut v));
}
fn upper_if_odd<'a>(s: &'a str, backing_store: &'a mut String) -> &'a str {
if s.len() % 2 == 1 {
*backing_store = s.to_uppercase();
backing_store
} else {
s
}
}
&write
would enable writing something like:
fn main() {
let v;
println!("{}", upper_if_odd("blah", &write v));
}
fn upper_if_odd<'a>(s: &'a str, backing_store: &'a write String) -> &'a str {
if s.len() % 2 == 1 {
*backing_store = s.to_uppercase();
backing_store
} else {
s
}
}
The caller declares the binding, so has control over scope and dropping, and the &write
effectively instructs the compiler to treat upper_if_odd
as inlined for the purposes of its initialisation analysis.
By framing this as a kind of borrow rather than a new kind of returned lifetime, and by treating it as an instruction to existing analysis, this feels like a much smaller and more consistent addition the the language.
Its concrete benefits over &mut
are:
- Avoids the boilerplate and overhead of unnecessary
Option
s. - Avoids adding extra
unsafe
withMaybeUninit
s.
I feel like this is low enough boilerplate to avoid needing extra sugar as @dhm is suggesting around with_slots
...
It opens up the possibility for &write impl Trait
, and if we end up with auto-generated sum types (a la RFC 2414, and some IRLO threads) these could extend fairly naturally to &write
to drive the ergonomics for multiply-sized values in a consistent way, similar to how @petertodd was describing with automatic enum derivation.
I'm not sure I see a clear path to unsized support, though...