代码之家  ›  专栏  ›  技术社区  ›  freakish

如何将混凝土类型铸造为通用(手动专业化)

  •  0
  • freakish  · 技术社区  · 8 月前

    我正在写一些代码,我想把常见的功能封装在一个地方。这是代码

    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 实施

    我已经运行了一些测试,它是有效的。

    这个设计可以吗?我遗漏了什么缺点吗?或者有更好的方法来实现我想要的吗?

    1 回复  |  直到 8 月前
        1
  •  2
  •   Chayim Friedman    8 月前

    除非你 专门从事 很明显。换句话说,除非您对除X之外的所有类型都有默认实现。

    由于你只想列出一些类型,这很容易得到正常特征的支持:

    pub trait Process: Sized {
        fn process() -> Result<Self, X>;
    }
    
    impl Process for Foo {
        fn process() -> Result<Self, X> {
            process_foo()
        }
    }
    
    impl Process for usize {
        fn process() -> Result<Self, X> {
            process_usize()
        }
    }
    
    pub fn process<T: Process>() -> Result<T, X> {
        T::process()
    }