我正在写一些代码,我想把常见的功能封装在一个地方。这是代码
pub struct Foo { }
pub fn process_foo() -> Result<Foo, X>;
pub fn process_usize() -> Result<usize, X>;
现在我可以将这些功能分开处理,但我觉得有点难看。所以我想把它们包装成一个通用的:
pub fn process<T>() -> Result<T, X>;
当然,Rust还不支持专门化(至少在稳定的通道上),所以我不能真正专门化这个方法。但我没有太多类型,所以我想出了以下解决方法:
pub enum TypeInfo {
Usize,
Foo,
}
pub trait WithTypeInfo {
fn type_info() -> TypeInfo;
}
impl WithTypeInfo for Foo {
fn type_info() -> TypeInfo { TypeInfo::Foo }
}
impl WithTypeInfo for usize {
fn type_info() -> TypeInfo { TypeInfo::Usize }
}
我修改了我的
process<T>
允许
T: WithTypeInfo
只有有了这个,我有:
pub fn process<T: WithTypeInfo>() -> Result<T, X> {
match T::type_info() {
TypeInfo::Foo => process_foo(),
TypeInfo::Usize => process_usize(),
}
}
这当然不会编译,因为Rust不知道如何转换
Result<Foo, X>
到
Result<T, X>
和
Result<usize, X>
到
结果<T、 X>
。尽管我已经向自己保证,在这两种情况下,它们都是相同的类型。
那么如何应对呢?好吧,我找到的唯一选项是通过一个不安全的宏:
pub fn process<T: WithTypeInfo>() -> Result<T, X> {
macro_rules! mutate {
( $e:expr ) => {
{
let item = { $e }?;
let src = core::ptr::from_ref(&item).cast::<T>();
let dst = unsafe { core::ptr::read(src) };
#[allow(forgetting_copy_types, clippy::forget_non_drop)]
{
std::mem::forget(item);
}
Ok(dst)
}
};
match T::type_info() {
TypeInfo::Foo => mutate!(process_foo()),
TypeInfo::Usize => mutate!(process_usize()),
}
}
这是有效的。所以这个宏所做的……几乎什么都没有。它接受对象并将其重新解释为
T
。我需要补充
std::mem::forget(item);
因为我最终得到了同一个物体的两个副本,其中一个不得不被遗忘。在我的真实场景中,其中一些对象具有适当的
Drop
实施
我已经运行了一些测试,它是有效的。
这个设计可以吗?我遗漏了什么缺点吗?或者有更好的方法来实现我想要的吗?