【问题标题】:Automatically implement traits of enclosed type for Rust newtypes (tuple structs with one field)自动为 Rust 新类型实现封闭类型的特征(具有一个字段的元组结构)
【发布时间】:2014-09-09 23:30:30
【问题描述】:

在 Rust 中,可以创建只有一个字段的元组结构,如下所示:

struct Centimeters(i32);

我想用Centimeters 做基本的算术运算,而不是每次都用模式匹配提取它们的“内部”值,也不想实现AddSub、...特征和重载运算符。

我想做的是:

let a = Centimeters(100);
let b = Centimeters(200);
assert_eq!(a + a, b);

【问题讨论】:

    标签: struct traits rust newtype


    【解决方案1】:

    我为这个问题制作了derive_more crate。它可以为元素实现它们的结构派生许多特征。

    您需要将derive_more 添加到您的Cargo.toml。然后你可以写:

    #[macro_use]
    extern crate derive_more;
    
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Add)]
    struct Centimeters(i32);
    
    fn main() {
        let a = Centimeters(100);
        let b = Centimeters(200);
        assert_eq!(a + a, b);
    }
    

    【讨论】:

    【解决方案2】:

    有没有一种方法可以做到这一点,而无需每次使用模式匹配提取它们的“内部”值,也无需实现 Add、Sub、... 特征和重载运算符?

    不,唯一的方法是手动实现特征。 Rust 没有与 the Haskell's GHC extension GeneralizedNewtypeDeriving 等效的方法,它允许包装器类型上的 deriving 自动实现被包装类型实现的任何类型类/特征(并且使用 Rust 的 #[derive] 的当前设置作为简单的 AST 转换,像 Haskell 一样实现它基本上是不可能的。)

    为了简化这个过程,你可以使用宏:

    use std::ops::{Add, Sub};
    
    macro_rules! obvious_impl {
        (impl $trait_: ident for $type_: ident { fn $method: ident }) => {
            impl $trait_<$type_> for $type_ {
                type Output = $type_;
    
                fn $method(self, $type_(b): $type_) -> $type_ {
                    let $type_(a) = self;
                    $type_(a.$method(&b))
                }
            }
        }
    }
    
    #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
    pub struct Centimeters(i32);
    
    obvious_impl! { impl Add for Centimeters { fn add } }
    obvious_impl! { impl Sub for Centimeters { fn sub } }
    
    #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
    pub struct Inches(i32);
    
    obvious_impl! { impl Add for Inches { fn add } }
    obvious_impl! { impl Sub for Inches { fn sub } }
    
    fn main() {
        let a = Centimeters(100);
        let b = Centimeters(200);
        let c = Inches(10);
        let d = Inches(20);
        println!("{:?} {:?}", a + b, c + d); // Centimeters(300) Inches(30)
        // error:
        // a + c;
    }
    

    playpen

    我在宏中模拟了普通的impl 语法,以便通过查看宏调用(即减少查看宏定义的需要)来明确发生的情况,并保持 Rust 的自然可搜索性:如果您正在寻找 Centimeters 上的特征,只需 grep for for Centimeters,您会发现这些宏调用以及普通的 impls。

    如果您经常访问 Centimeters 类型的内容,您可以考虑使用带有字段的适当结构来定义包装器:

    struct Centimeters { amt: i32 }
    

    这允许您编写self.amt 而不必进行模式匹配。您还可以定义一个类似fn cm(x: i32) -&gt; Centimeters { Centimeters { amt: x } } 的函数,调用类似cm(100),以避免构建完整结构的冗长。

    您还可以使用.0.1 语法访问元组结构的内部值。

    【讨论】:

    • 我几乎可以肯定宏是要走的路,但我还没有掌握它们:) 如果您建议正确的结构,那么单字段结构的用例到底是什么?跨度>
    • @anonymous_user_13 你的意思是一个单字段元组结构?默认的构造语法更好,我能想到的唯一好处是不必考虑字段的名称。
    【解决方案3】:

    对于 Rust 1.10.0 版,在我看来类型别名非常适合您所描述的情况。他们只是给一个类型一个不同的名字。

    假设所有厘米都是u32s。然后我就可以使用代码了

    type Centimeters = u32;
    

    u32 拥有的任何特征,Centimeters 都会自动拥有。这并不能消除将Centimeters 添加到Inches 的可能性。如果你小心点,你就不需要不同的类型。

    【讨论】:

    • 如果你小心,你就不需要不同的类型。——如果你足够小心,你就不需要像 Rust 这样的语言 ^_^。类型应该对程序员有所帮助。
    • 同意只是提供一个潜在的想法
    • D 通过引入 类型别名(如上)和 新类型(类似,但没有隐式转换),进一步走上了这条路线。
    猜你喜欢
    • 2021-06-07
    • 2022-08-15
    • 2020-01-03
    • 2022-11-23
    • 2019-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多