there’s been a lot of talk about &uninit
references, MaybeUninit
, placement-new…
I’d like to propose an alternative to all of them: Partially Initialized Types. They’re, arguably, the cleanest approach to all of this.
Consider types:
struct Foo { a: i32, b: i32 }
struct Bar { a: i32, b: Foo }
Then, we can have:
let x: Bar(b(a)) = Bar { b: Foo { a: 1 } };
However, passing these to functions is mostly pointless:
fn foo(x: &mut Bar()) {
// can't do anything with x here
}
foo(x); // but can still call it
So we need a way to specify type/state mutations:
fn bar(x: &mut Bar() -> Bar(a)) {
x.a = 2;
}
bar(x);
assert_eq!(x.a, 2);
assert_eq!(x.b.a, 1);
What you write and how the compiler looks at it are slightly different: if we accept -
to mean uninitialized, ~
to mean ignored, and +
to mean initialized, we generally have:
Foo() = Foo(~a, ~b)
Foo(a) = Foo(+a, ~b)
fn foo(x: Foo() -> Foo(b)) = fn foo(x: Foo(~a, -b) -> Foo(~0, +1))
fn foo(x: Foo(b) -> Foo()) = fn foo(x: Foo(~a, +b) -> Foo(~a, -b))
With ~
being taken as -
in object position (non-references, basically - return types, let bindings, etc). So:
let x: Foo(a) = ...;
let y: Foo() = x; // deinitializes x.a, without dropping x.
You can then have, with specialization:
fn place_back() -> Place<T()> {
// allocate stuff
}
impl Drop for Place<T()> {
fn drop(&mut self) {
self.vec.deallocate();
}
}
impl Drop for Place<T(..)> {
fn drop(&mut self) {
// it's initialized, don't do anything
}
}
Finally, this is the discussion that led to this: https://github.com/rust-lang/rfcs/pull/2534