At first: currently we already have &dyn and &mut dyn kinds, they handle known cases of dynamic dispatch, and from them object safety rules arose;\
Given that the goal we are pursuing is allowing static existential types (impl Trait) to be coercible to dynamic existential types (dyn Trait) we just need to get "owning dyn types".
In Niko's blog post dyn* types are, essentially, fat pointer:
Creating a dyn*
To coerce a value of type T into a dyn* Trait , two constraints must be met:
- The type
T must be pointer-sized or smaller.
- The type
T must implement Trait
In the real world, however, what will actually be coerced to dyn* Trait is an owning smart pointer, such as Box<T,_> or StaticRc<1,1>. Thus, given vtable's, drops and custom allocators I think we should take a different approach: concrete layout of dyn* Trait type incorporates all these notions and we get a trait to construct dyn* Trait from appropriate types.
(Warning: I used generic traits concept a lot)
My preferred design is therefore:
/// this trait is implemented for everything that can be coerced into a `dyn*` type
/// this is unsafe, because the only types that should implement this must logically own their values (like `Box`)
unsafe trait CoerceDynStar<trait Trait>
where Self: Deref, <Self as Deref>::Target: Trait
{
//this is called on `dyn* Trait` value drop - it frees memory owned by the value
fn drop(&mut self);
//this is called for creating a `dyn*` value from a container
fn create(self) -> dyn* Trait;
}
///`dyn *` types are, in fact, this:
struct DynStar<trait Trait>{
data: Unique<[u8]> //owning data pointer (1)
vtable: VTable<Trait> //vtable ref
drop: fn(&mut self) //drop routine, shouldn't be a part of `vtable` field (see below) (2)
allocator: &dyn Allocator //reference to an allocator which we got memory from*
}
* I know that currently allocator api isn't object safe (in old sense), but hope that this will change.
(1) This is the source of the lifetime 'x here dyn* Trait + 'x
(2) Moreover, this function pointer is only valid for the lifetime of allocator, which we have taken the memory from.
Thus, our API get more lifetimes:
//imaginary syntax warning
unsafe trait CoerceDynStar<trait Trait>
where Self: Deref, <Self as Deref>::Target: Trait
{
fn drop(&mut self);
fn create(self) -> dyn* Trait; //see below
}
struct DynStar<'d,'a: 'd,trait Trait>{
data: Unique<[u8]> + 'd,
vtable: VTable<Trait>,
drop: fn(&mut self),
allocator: &'a dyn Allocator
}
Since the structure exists only during 'd lifetime, not extending to 'a, we have only one lifetime we care about.
Given we are designing new kind of types I wonder if it'd be better if apply the same lifetime elision rules we have for output references to that single lifetime of dyn* kind? It'd allow writing (and transforming to ) fn method(&self) -> dyn* Trait instead of fn method(&self) -> dyn* Trait + '_
An example:
impl<O,'a,F: Future<Output = O>> CoerceDynStar<Future> for Box<F,&'a dyn Allocator> { //bad thing about generic trait happened
fn drop(&mut self) {
std::mem::drop_in_place(self);
}
fn create(self) -> dyn* Future<Output = O> { // (+ 'a) is unspecified
DynStar::<'_,'_,Future>{
data: self.ptr,
vtable: VTable<Future>::for::<F>(), //this return vtable pointer\reference
drop: <F as Drop>::drop,
allocator: self.alloc, //alloc reference, hence attached lifetime
}
}
}
Dropping of any dyn* Trait type is done in two steps: call (self.drop)(self.data) and call self.alloc.dealloc(self.data).
P.S. Are there currently any cases that are not Box?