While it is certainly niche, the inability to unsafely impl Copy and Freeze seems to have recently actually been a barrier to some code I've been trying to write.
Now, obviously given the compiler integration with these traits, impling them is massively unsafe.
However, the inability to declare both an instance of Drop
and an instance of Copy
is actually a barrier for some (also massively unsafe) code.
In essence the place it has been coming up has been with cases where I basically need to manually implement drop glue due to some details. While the exact details are unimportant, here is a simplified example.
Suppose rust did not have native enums, and I was trying to implement an option type like so:
struct MyOption<T> {
active: bool,
data: MaybeUninit<T>,
}
impl<T: Clone> Clone for MyOption<T> {
fn clone(&self) -> Self {
if self.active {
return MyOption {
active: true,
data: MaybeUninit::new(unsafe { self.data.assume_init_ref() }.clone()),
};
} else {
return MyOption {
active: false,
data: MaybeUninit::uninit(),
};
}
}
}
impl<T: Copy> Copy for MyOption<T> {}
impl<T> Drop for MyOption<T> {
fn drop(&mut self) {
if self.active {
unsafe { drop_in_place(self.data.assume_init_mut()) }
}
}
}
I claim this is perfectly sensible code, though unnecessary in this particular case. Notably, this does not require a few guarantees that would be potentially problematic to give:
- That drop is called when copy is satisfied
- That drop is not called when copy is satisfied
Not requiring these guarantees means that the compiler does not have to be careful about what is a move vs a copy. Perhaps there will be extra calls to drop in the generated code. They are no-ops (unless T
itself impls both Copy
and Drop
and has a drop that is itself not a no-op, which is precisely the thing that would be sketchy, and would I believe prove equally problematic to the enum Option
).
This is the primary issue of importance to me with Copy
, though I note the inability to impl Copy
on a type without a drop implementation including drop glue if it wasn't impl
ed on one of the inner types is a hole in what unsafe
can easily do. If a library author doesn't want to promise that a type will continue to be copyable and so hasn't declared it, there is to my knowledge no remotely sane way to declare that your type which uses it is Copy
. Such a thing would obviously be a hazard when updating dependencies, but doesn't seem as though it must be impossible.
As for Freeze, I do not believe there is currently any way to take a type variable T
and have a field that has all the same niches and so on but also benefits from Freeze
optimizations. Alternatively, a UnsafeAnticell<T>
(bikeshedding probably required) that unconditionally implements freeze but shares the representation may be a better solution, as it can more safely encapsulate the unsafety of even giving out a &T
. The meaning of such a type however seems quite straightforward. UnsafeAnticell<UnsafeCell<T>> = T
. UnsafeAnticell<Option<UnsafeCell<T>>=
... actually this one is a bit more complicated, but with a NoNicheWrapper
it becomes UnsafeAntiCell<Option<NoNicheWrapper<T>>
.
This is also relevant for some nonsense where I can guarantee immutability of certain regions of memory based on more complicated criteria. As Freeze
is primarily an optimization, it is probably less of an issue than Copy
, though I will note that const
stuff depends on Freeze
.
The Copy
issue can also be resolved with a wrapper type naturally, though I think that is less natural than UnsafeAntiCell
type things.