The missing reference type: write-only
⚓ Rust 📅 2026-03-21 👤 surdeus 👁️ 1I find myself wishing for a write-only reference type, and I'm curious to get feedback on this idea.
| read | | & | shared reference
| read | write | &mut | mutable reference
| | write | &write | write-only reference
The use case is for working with uninitialized data safely:
// STDLIB
impl<T, A> Vec<T, A> {
/// returns the data between len and cap
pub fn spare_capacity_write(&write self) -> &write [T] {
...
}
}
// UPSTREAM CRATE: foo
/// fills some number n bytes of dst and returns n
pub fn decompress(src: &[u8], dst: &write [u8]) -> usize {
... // assign values to dst, but never read from it
}
// DOWNSTREAM CRATE: bar
fn main() {
let (src0, src1) = ...;
let mut dst = Vec::with_capacity(...);
// write to uninitialized data:
let bytes_written = decompress(&src0, dst.spare_capacity_write());
unsafe { dst.set_len(bytes_written); }
... // do something with the now safely initialized dst
// now reuse dst, writing to initialized data:
decompress(&src1, dst.as_mut_slice()); // mut references can be cast to write
... // do something with dst again
}
In terms of borrow checking, &write would behave exactly the same as &mut. The difference is it would be impossible to read anything from the &write, allowing us to create safe methods like spare_capacity_write. In contrast, here's how to do this same thing today with MaybeUninit:
// STDLIB
impl<T, A> Vec<T, A> {
/// returns the data between len and cap
pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<T>] {
...
}
}
// UPSTREAM CRATE: foo
trait WriteableSlice<T> {
fn into_maybe_uninit(self) -> &mut [MaybeUninit<T>];
}
impl<T> WriteableSlice<T> for &mut [T] {
fn into_maybe_uninit(self) -> &mut [MaybeUninit<T>] {
mem::transmute(self)
}
}
impl<T> WriteableSlice<T> for &mut [MaybeUninit<T>] {
fn into_maybe_uninit(self) -> &mut [MaybeUninit<T>] {
self
}
}
/// fills some number n bytes of dst and returns n
pub fn decompress<Dst: WriteableSlice<u8>>(src: &[u8], dst: Dst) -> usize {
... // assign values to dst, but never read from it
}
// DOWNSTREAM CRATE: bar
fn main() {
let (src0, src1) = ...;
let mut dst = Vec::with_capacity(...);
// write to uninitialized data:
let bytes_written = decompress(&src0, dst.spare_capacity_mut());
unsafe { dst.set_len(bytes_written); }
... // do something with the now safely initialized dst
// now reuse dst, writing to initialized data:
decompress(&src1, dst.as_mut_slice()); // mut references can be cast to write
... // do something with dst again
}
Reasons I like this idea:
- We no longer need to use traits and generics for the decompression API in
footo accept both initialized and uninitialized data. - The user in
barnow has a simpler signature to work with. - It's now more guaranteed that we get a single compiled function in the assembly, rather than relying on optimization passes.
- It's easier to assign a regular value (
dst[i] = x) than a MaybeUninit (dst[i] = MaybeUninit::new(x)).
Of course, I recognize that adding a new type of reference would be a major undertaking, and I don't have a formal proposal for this. But I'm curious to hear what people think.
6 posts - 4 participants
🏷️ Rust_feed