Skip to content

Instantly share code, notes, and snippets.

@joonazan

joonazan/lib.rs Secret

Created October 18, 2022 22:08
Show Gist options
  • Save joonazan/32fe6627e320325e7182c072ff8461d0 to your computer and use it in GitHub Desktop.
Save joonazan/32fe6627e320325e7182c072ff8461d0 to your computer and use it in GitHub Desktop.
Minimal version of bad drop codegen for coproduct
use std::any::TypeId;
use std::mem::ManuallyDrop;
#[repr(C)]
pub union Union<A, B> {
pub(crate) head: ManuallyDrop<A>,
pub(crate) tail: ManuallyDrop<B>,
}
pub enum EmptyUnion {}
/// Trait for properly deallocating Unions that are not Copy.
///
/// Unlike the other Indexed* traits, this one is exported because there is no
/// way to avoid mentioning it. For example to require IndexedClone it suffices
/// to require that `Coproduct<T>` is [Clone].
pub trait IndexedDrop {
/// # Safety
/// The argument `i` must be the type id of the type stored in the Union.
unsafe fn idrop(&mut self, i: TypeId);
}
impl<H: 'static, T: IndexedDrop> IndexedDrop for Union<H, T> {
unsafe fn idrop(&mut self, i: TypeId) {
if i == TypeId::of::<H>() {
ManuallyDrop::drop(&mut self.head)
} else {
self.tail.idrop(i)
}
}
}
impl IndexedDrop for EmptyUnion {
#[inline]
unsafe fn idrop(&mut self, _: TypeId) {
match *self {}
}
}
/// Changes type to ANYTHING.
/// # Safety
/// Only use this on repr(C) unions. The output union must be able to contain
/// the active variant of the input union.
pub unsafe fn union_transmute<X, Y>(x: X) -> Y {
#[repr(C)]
union Transmuter<A, B> {
before: ManuallyDrop<A>,
after: ManuallyDrop<B>,
}
ManuallyDrop::into_inner(
Transmuter {
before: ManuallyDrop::new(x),
}
.after,
)
}
/// This trait is implemented for Unions where variant I has type X.
pub trait UnionAt<I, X> {
/// Create a union that contains the given value.
fn inject(x: X) -> Self;
/// Convert a union to the contained type.
/// # Safety
/// If the active variant of the coproduct is not at index I,
/// calling this method is undefined behaviour.
unsafe fn take(self) -> X;
/// The coproduct minus its Ith variant
type Pruned;
}
pub struct Here;
pub struct There<T>(T);
impl<X, Rest> UnionAt<Here, X> for Union<X, Rest> {
fn inject(x: X) -> Self {
Union {
head: ManuallyDrop::new(x),
}
}
unsafe fn take(self) -> X {
ManuallyDrop::into_inner(self.head)
}
type Pruned = Rest;
}
impl<I, X, H, T> UnionAt<There<I>, X> for Union<H, T>
where
T: UnionAt<I, X>,
{
fn inject(x: X) -> Self {
Union {
tail: ManuallyDrop::new(T::inject(x)),
}
}
unsafe fn take(self) -> X {
ManuallyDrop::into_inner(self.tail).take()
}
type Pruned = Union<H, T::Pruned>;
}
pub struct Coproduct<T: IndexedDrop> {
tag: TypeId,
union: T,
}
impl<T: IndexedDrop> Drop for Coproduct<T> {
fn drop(&mut self) {
unsafe { self.union.idrop(self.tag) }
}
}
pub trait At<I, X> {
fn inject(x: X) -> Self;
fn uninject(self) -> Result<X, Self::Pruned>;
type Pruned;
}
impl<I, X: 'static, U: IndexedDrop> At<I, X> for Coproduct<U>
where
U: UnionAt<I, X>,
U::Pruned: IndexedDrop,
{
fn inject(x: X) -> Self {
Self {
tag: TypeId::of::<X>(),
union: U::inject(x),
}
}
fn uninject(self) -> Result<X, Self::Pruned> {
let res = if self.tag == TypeId::of::<X>() {
Ok(unsafe { std::ptr::read(&self.union).take() })
} else {
Err(Coproduct {
tag: self.tag,
union: unsafe { union_transmute(std::ptr::read(&self.union)) },
})
};
std::mem::forget(self);
res
}
type Pruned = Coproduct<U::Pruned>;
}
type TestCoprod = Coproduct<Union<u8, Union<u16, Union<u32, Union<i32, EmptyUnion>>>>>;
pub fn uninject_coprod(c: TestCoprod) -> Option<u32> {
c.uninject().ok()
}
pub fn drop_coprod(c: TestCoprod) {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment