Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Making a type-erasing function idempotent
Consider 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 SomeTrait
know about all the kinds of type erasure we want โ for example, if you want Box<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 unsafe
and requires T: 'static
.
Compare type IDs and downcast. This is tricky, since dyn Any
is unsized and the purpose is avoiding heap allocation, but can be done using a combination of &mut
and Option
:
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:
SomeTrait
, and unlike option 3, does not require T: '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 from SomeTrait
, but I donโt think thereโs a way to achieve that short of separately โ not generically โ implementing it for every SomeTrait
implementor.)5 posts - 3 participants
๐ท๏ธ Rust_feed