Summary
There is currently a lot of pain around unsizing and custom unsized types in Rust. This proposal does not attempt to address every possible use-case or problem around the current system, but does attempt to address several current limitations in terms of improving existing tools. It does this by loosening restrictions on several existing unstable traits.
Motivation
Currently, writing custom unsized types in Rust is very painful. To initialize one tends to require large amounts of unsafe code, and to write items handling them is similarly painful, due to restrictions on CoerceUnsized. This proposal does not fix all use cases, but does make initializing custom unsized types and writing wrappers for them significantly less painful in some common cases.
Guide-Level Explanation
Unsize
Changes
Unsize
can now be implemented (following normal trait impl rules) by users on types that fulfill the following conditions, given a pair of types T: Sized
and U: !Sized
:
-
T
has the same repr asU
-
T
has the same fields asU
, excepting the last field - The scope within which the
Unsize
impl resides can 'see' all of bothT
andU
's fields. Same module for private fields, same crate forpub(crate)
fields, dependent crates for public fields- For items from other crates,
U
must be exhaustive
- For items from other crates,
- for
T
's last fieldTLast
andU
's last fieldULast
,TLast: Unsize<ULast>
Examples
The following examples result in a type that can be unsized into another succesfully:
pub struct Object {
name: String,
fields: [*const Field],
}
pub struct SizedObject<const N: usize> {
name: String,
fields: [*const Field; N],
}
impl<const N: usize> Unsize<Object> for SizedObject<N> {}
#[repr(C)]
pub struct DebugableItem {
count: i32,
debug: dyn Debug,
}
#[repr(C)]
pub struct Field1 {
count: i32,
name: String,
}
impl Unsize<DebugableItem> for Field1 {}
#[repr(C)]
pub struct Field2 {
count: i32,
idx: i32,
}
impl Unsize<DebugableItem> for Field2 {}
The following examples do not result in a type that can be unsized into another:
#[repr(C)]
pub struct CSlice([u8]);
pub struct RustSlice<const N: usize>([u8; N]);
// Errors: repr mismatch
impl<const N: usize> Unsize<CSlice> for RustSlice<N> {}
mod private {
pub struct Foo {
a: [bool],
}
}
pub struct Bar {
a: [bool; 10],
}
// Error: Foo fields no visible
impl Unsize<Foo> for Bar {}
CoerceUnsized
Changes
CoerceUnsized
will no longer be limited to only being implemented on structs. Enums will be allowed to implement it, given that they follow the following rules:
- The type to coerce (
T
) exists at least once in a non-phantomdata field in the enum -
T
exists at most once in a non-phantomdata field in each variant - Given each variant containing a type using
T
, and the type to coerce to (U
),CoerceUnsized<Foo<U>> for CoerceUnsized<Foo<T>>
must be implemented for the containing type
Examples
The following examples compile successfully
pub enum MaybeMut<T> {
Foo(*const T),
Bar(*mut T),
}
impl CoerceUnsized<MaybeMut<U>> for MaybeMut<T>
where
T: ?Sized + Unsize<U>,
U: ?Sized,
{}
pub enum CoercableOption<T> {
Some(&mut T),
None,
}
impl CoerceUnsized<CoercableOption<U>> for CoercableOption<T>
where
T: ?Sized + Unsize<U>,
U: ?Sized,
{}
The following examples do not
pub enum MissingT<T> {
Foo(i32),
Bar(bool),
}
impl CoerceUnsized<MissingT<U>> for MissingT<T>
where
T: ?Sized + Unsize<U>,
U: ?Sized,
{}
pub enum TooManyT<T> {
Field(*const T, *mut T),
Missing,
}
impl CoerceUnsized<TooManyT<U>> for TooManyT<T>
where
T: ?Sized + Unsize<U>,
U: ?Sized,
{}
Reference-level explanation
Unsize
Changes
The compiler already automatically generates all Unsize
impls, this proposal will simply expand the cases it works on. The code generated should look similar to existing generated code.
CoerceUnsized
Changes
The code generated by the compiler for coerce-unsizing enums will follow a fairly simple format:
match self {
Var1 { field_0, .., field_n } => Var1 { field_0, .., field_n as &mut U },
..,
VarN { field_0, .., field_n } => VarN { field_0 as &U, .., field_n },
}
Drawbacks
TODO
Alternatives
- User-controlled unsizing
- Limits ergonomics, no
Box::new(foo) as Box<Bar>
- Requires unsafe to implement, manual allocation
- Limits ergonomics, no
Unresolved questions
- Should any more situations be restricted/allowed?
- Should downstream crates never implement
Unsize<Upstream>
?
- Should downstream crates never implement
- Will this lock us out of any future alternatives?
Future Possibilities
- Allowing unsizing based on fields (len field in a struct)