Making a type-erasing function idempotent
โ Rust ๐ 2025-08-22 ๐ค surdeus ๐๏ธ 13Consider the following program which is performing type erasure involving boxing:
trait SomeTrait: core::fmt::Debug {}
impl SomeTrait for u32 {}
impl SomeTrait for Box<dyn SomeTrait> {}
fn into_boxed<T: SomeTrait + 'static>(value: T) -> Box<dyn SomeTrait> {
Box::new(value)
}
#[test]
fn not_double_boxing() {
let b = into_boxed(123);
let a1 = &raw const *b;
let b = into_boxed(b);
let a2 = &raw const *b;
println!("{b:?}");
assert_eq!(a1, a2);
}
The test fails, of course, because each time into_boxed() is called, it adds a new Box. But suppose I want to avoid double boxing, and have into_boxed() return its input if that input is Box<dyn SomeTrait>. I know of three ways to do this, all of which will make the above test pass:
-
Include a special method in
SomeTrait:trait SomeTrait: core::fmt::Debug { fn into_boxed(self) -> Box<dyn SomeTrait> where Self: Sized + 'static, { Box::new(self) } } impl SomeTrait for Box<dyn SomeTrait> { fn into_boxed(self) -> Box<dyn SomeTrait> { self } }This is straightforward, using provided methods to give a somewhat specialization-like effect. However, it requires that
SomeTraitknow about all the kinds of type erasure we want โ for example, if you wantBox<dyn SomeTrait + Send>, the trait has to include a method for that too. -
Compare type IDs and transmute.
fn into_boxed<T: SomeTrait + 'static>(value: T) -> Box<dyn SomeTrait> { if TypeId::of::<T>() == TypeId::of::<Box<dyn SomeTrait>>() { // SAFETY: // * we just checked that the type is equal // * the duplication caused by `transmute_copy` is canceled out by `ManuallyDrop` unsafe { core::mem::transmute_copy(&ManuallyDrop::new(value)) } } else { Box::new(value) } }This requires no cooperation from the trait, but is
unsafeand requiresT: 'static. -
Compare type IDs and downcast. This is tricky, since
dyn Anyis unsized and the purpose is avoiding heap allocation, but can be done using a combination of&mutandOption:fn into_boxed<T: SomeTrait + 'static>(value: T) -> Box<dyn SomeTrait> { let value_in_option = &mut Some(value); if let Some(value_of_same_type) = <dyn Any>::downcast_mut::<Option<Box<dyn SomeTrait>>>(value_in_option) { value_of_same_type.take().unwrap() } else { Box::new(value_in_option.take().unwrap()) } }This has the same effect and performance as the transmute, and is safe, but inelegant to read.
My questions about all this are:
- Is there a more flexible way? In particular, one which, unlike option 1, does not require cooperation from
SomeTrait, and unlike option 3, does not requireT: 'static? I suspect not, but perhaps thereโs some angle on it I havenโt thought of. (It would be fine to use a trait separate fromSomeTrait, but I donโt think thereโs a way to achieve that short of separately โ not generically โ implementing it for everySomeTraitimplementor.) - Is there a way to write option 3 with less clutter, that is still safe?
5 posts - 3 participants
๐ท๏ธ Rust_feed