Skip to content

Commit

Permalink
feat(tricky-pipe): add try_cast to erased tx/rxs (#38)
Browse files Browse the repository at this point in the history
This commit adds new `SerReceiver::try_cast` and
`DeserSender::try_cast` methods, which attempt to recover a typed
`Receiver` or `Sender` from an erased `SerReceiver` or `DeserSender`,
respectively. These methods return an error if the requested type does
not match the original erased type.
  • Loading branch information
hawkw authored Nov 29, 2023
1 parent 6c68024 commit b441d3d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 2 deletions.
1 change: 1 addition & 0 deletions source/tricky-pipe/src/mpsc/arc_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ impl<T: 'static, E: Clone + 'static> TrickyPipe<T, E> {
clone: Self::erased_clone,
drop: Self::erased_drop,
type_name: core::any::type_name::<T>,
type_id: core::any::TypeId::of::<T>,
};

fn pipe(&self) -> TypedPipe<T, E> {
Expand Down
29 changes: 28 additions & 1 deletion source/tricky-pipe/src/mpsc/channel_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::loom::{
sync::atomic::{AtomicU16, AtomicUsize, Ordering::*},
};
use core::{
any::TypeId,
cmp, fmt,
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
Expand Down Expand Up @@ -111,6 +112,7 @@ pub(super) struct CoreVtable<E> {
pub(super) clone: unsafe fn(*const ()),
pub(super) drop: unsafe fn(*const ()),
pub(super) type_name: fn() -> &'static str,
pub(super) type_id: fn() -> TypeId,
}

pub(super) struct Vtables<T>(PhantomData<fn(T)>);
Expand Down Expand Up @@ -602,6 +604,31 @@ impl ErasedSlice {
// == impl ErasedPipe ===

impl<E> ErasedPipe<E> {
pub(super) fn typed<T: 'static>(self) -> Option<TypedPipe<T, E>> {
if TypeId::of::<T>() == (self.vtable.type_id)() {
Some(TypedPipe {
pipe: self,
_t: PhantomData,
})
} else {
None
}
}

/// Clone this `ErasedPipe` *without* incrementing the reference count. This
/// is intended to be used only when converting to a different reference
/// type, when the original `ErasedPipe` will not have its destructor run.
///
/// # Safety
///
/// Do NOT `Drop` this `ErasedPipe` after calling this method!!!!
pub(super) unsafe fn clone_no_ref_inc(&self) -> Self {
Self {
ptr: self.ptr,
vtable: self.vtable,
}
}

pub(super) fn core(&self) -> &Core<E> {
unsafe { &*(self.vtable.get_core)(self.ptr) }
}
Expand Down Expand Up @@ -665,7 +692,7 @@ impl<T: 'static, E> TypedPipe<T, E> {
/// Do NOT `Drop` this `TypedPipe` after calling this method!!!!
pub(super) unsafe fn clone_no_ref_inc(&self) -> Self {
Self {
pipe: self.pipe.clone(),
pipe: self.pipe.clone_no_ref_inc(),
_t: PhantomData,
}
}
Expand Down
43 changes: 42 additions & 1 deletion source/tricky-pipe/src/mpsc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::loom::cell::{self, CellWith, UnsafeCell};
use core::{
fmt,
future::Future,
mem::{self, MaybeUninit},
mem::{self, ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
pin::Pin,
ptr,
Expand Down Expand Up @@ -527,6 +527,27 @@ impl<E: Clone> SerReceiver<E> {
})
}

/// Attempts to cast this type-erased `SerReceiver` back to a typed
/// [`Receiver`]`<T, E>`, if this `SerReceiver` is associated with a channel
/// of `T`-typed messages.
///
/// # Returns
///
/// - [`Ok`]`(`[`Receiver`]`<T, E>)` if this `SerReceiver` is associated
/// with a T`-typed channel.
/// - [`Err`]`(SerReceiver)` if this `SerReceiver` is associated with a
/// channel with a message type other than `T`, allowing the
/// `SerReceiver` to be recovered.
pub fn try_cast<T>(self) -> Result<Receiver<T, E>, Self> {
// Don't drop this `SerReceiver`, as the return value will own this
// `SerReceiver`'s reference count.
let this = ManuallyDrop::new(self);
match unsafe { this.pipe.clone_no_ref_inc() }.typed::<T>() {
Some(pipe) => Ok(Receiver { pipe }),
None => Err(ManuallyDrop::into_inner(this)),
}
}

/// Close this channel with an error. Any subsequent attempts to send
/// messages to this channel will fail with `error`.
///
Expand Down Expand Up @@ -876,6 +897,26 @@ impl<E: Clone> DeserSender<E> {
.send_framed(bytes)
}

/// Attempts to cast this type-erased `DeserSender` back to a typed
/// [`Sender`]`<T, E>`, if this `DeserSender` is associated with a channel of
/// `T`-typed messages.
///
/// # Returns
///
/// - [`Ok`]`(`[`Sender`]`<T, E>)` if this `DeserSender` is associated with a
/// `T`-typed channel.
/// - [`Err`]`(DeserSender)` if this `DeserSender` is associated with a
/// channel with a message type other than `T`, allowing the `DeserSender` to be recovered.
pub fn try_cast<T>(self) -> Result<Sender<T, E>, Self> {
// Don't drop this `DeserSender`, as the return value will own this
// `DeserSender`'s reference count.
let this = ManuallyDrop::new(self);
match unsafe { this.pipe.clone_no_ref_inc() }.typed::<T>() {
Some(pipe) => Ok(Sender { pipe }),
None => Err(ManuallyDrop::into_inner(this)),
}
}

/// Close this channel with an error. Any subsequent attempts to send
/// messages to this channel will fail with `error`.
///
Expand Down
1 change: 1 addition & 0 deletions source/tricky-pipe/src/mpsc/static_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ where
clone: Self::erased_clone,
drop: Self::erased_drop,
type_name: core::any::type_name::<T>,
type_id: core::any::TypeId::of::<T>,
};

fn pipe(&'static self) -> TypedPipe<T, E> {
Expand Down

0 comments on commit b441d3d

Please sign in to comment.