It'll be good I promise
Currently the most active DST proposal seems to be #2594. After reading it, my impression is that it tries to mainly address two use cases:
- DST's like C-strings or C:s flexible array members
- Custom pointers/references for things like BitVec, Ndarray, etc.
Personally I believe n. 2 can be solved with less restrictive Deref, Index and IndexMut traits (for example #2953).
This Pre-RFC addresses the first use case.
DST's with slim pointers
The standard library exposes a new trait (that is auto-implemented for sized types) :
pub unsafe trait KnownSize {
fn get_size(&self) -> usize;
}
Implementing this trait for T: ?Sized
has the following consequences:
- Pointers and references to
T
are now one word wide - Where previously the size field of the fat pointer would be inspected, get_size is called
That's it. Here is an implementation of CStr:
use std::os::raw::c_char;
// Needed for unsafe code
#[repr(transparent)]
// Syntax is the same as for regular unsized structs
struct CStr ([c_char]);
unsafe impl KnownSize for CStr {
fn get_size(&self) -> usize {
// Finds the first null character
// Similarly to implementing drop we have to be careful not to cause infinite recursion
let mut ptr = self as *const Self as *const c_char;
let mut count = 1usize;
unsafe {
while *ptr != 0 {
count+= 1;
ptr = ptr.add(1);
}
}
count
}
}
The standard library can have the following struct to make custom DSTs 100% safe
struct SizeWrapper<T: ?Sized> {
size: usize,
contents: T,
}
unsafe impl<T: ?Sized> KnownSize for SizeWrapper<T> {
fn get_size(&self) -> usize {
self.size
}
}
// SizeWrapper can implement this
impl<Idx :?Sized, T: ?Sized + Index<Idx>> Index<Idx> for SizeWrapper<T> {
fn index(&self, index: Idx) -> &T::Index::Output {
// This implicitly calls get_size because it takes a reference to a ?Sized + !KnownSize field
self.contents.index(index)
}
}
// The same for IndexMut and Deref
the user code can just do
struct MyDST (SizeWrapper<str>);
// MyDST is ?Sized and derives KnownSize
impl MyDST {
fn foo(&self) {
println!("MyDST has a message for you: {}", self.0);
}
}
But it's still impossible to create and use DST's ergonomically using safe code. For this I propose the following:
KnownSize ergonomics
Types that are ?Sized + KnownSize
can be moved, but you can't assign them without binding.
What this means is that:
let foo = MyDST::from_str("Hello"); // Ok
let mut foo = foo.push_str(" World"); // Also ok
let r = &mut foo; // Still ok
*r = MyDST::new("This string can't fit") // Error: cannot assign without binding
foo = MyDST::new("Neither can this") // Error: cannot assign without binding
The allowed moves must happen either in function calls (arguments and return values) or when binding values. In both cases the nature of the stack allows them to be unsized, just like in C. Except in async functions ... where the returned future would have to be ?Sized + KnownSize if the state machine ever contains a ?Sized value ... sigh.
Finally, what about dyn Foo
? Here's what:
- Every
dyn Foo
implements KnownSize (so its reallydyn Foo + KnownSize
). According to my understanding, this is the case today, as Box for example has to know it's size. - If
T: KnownSize + Foo
, then it's possible to get a&dyn Foo
to a T
Unless I've overlooked something, this should be backwards compatible, since KnownSize is just a less strict subset of ?Sized. Only breaking changes are nomicon related ones, such as someone assuming that a ?Sized ptr or ref must be two words wide.
Pros:
- IMO simpler (to implement?) than the current proposals
- Allows usable DST:s
Cons:
- This is really a subset of #2594
- Cannot work with async fn:s without compromises