如何创建没有样板代码的只读结构?

时间:2018-06-19 13:38:41

标签: functional-programming rust

尽管Rust吸收了许多好的现代编程思想,但似乎并没有提供一项非常基本的功能。

现代(伪)功能代码基于以下种类的大量类:

pub struct NamedTuple {
    a: i8,
    b: char,
}
impl NamedTuple {
    fn new(a: i8, b: char) -> NamedTuple {
        NamedTuple { a: a, b: b }
    }
    fn a(&self) -> i8 {
        self.a
    }
    fn b(&self) -> char {
        self.b
    }
}

如您所见,这里有很多样板代码。没有样板代码,真的没有办法紧凑地描述这些类型吗?

2 个答案:

答案 0 :(得分:11)

当您有了样板时,请考虑

macro_rules! ro {
    (
        pub struct $name:ident {
            $($fname:ident : $ftype:ty),*
        }
    ) => {
        pub struct $name {
            $($fname : $ftype),*
        }

        impl $name {
            fn new($($fname : $ftype),*) -> $name {
                $name { $($fname),* }
            }

            $(fn $fname(&self) -> $ftype {
                self.$fname
            })*
        }
    }
}

ro!(pub struct NamedTuple {
    a: i8,
    b: char
});

fn main() {
    let n = NamedTuple::new(42, 'c');
    println!("{}", n.a());
    println!("{}", n.b());
}

这是一个基本的宏,可以扩展以处理指定的可见性以及结构和字段上的属性/文档。

我要挑战的是,您拥有与您想象的一样多的样板。例如,您仅显示Copy类型。在结构中添加StringVec后,它就会崩溃,您需要返回引用或获取self


从编辑上来说,我认为这不是好的或惯用的Rust代码。如果您有需要人们挖掘的值类型,请仅将字段设为公开

pub struct NamedTuple {
    pub a: i8,
    pub b: char,
}

fn main() {
    let n = NamedTuple { a: 42, b: 'c' };
    println!("{}", n.a);
    println!("{}", n.b);
}

现有的Rust功能可防止吸气剂方法试图解决的大多数问题。

基于绑定的可变性

n.a = 43;
error[E0594]: cannot assign to field `n.a` of immutable binding

引用规则

struct Something;

impl Something {
    fn value(&self) -> &NamedTuple { /* ... */ }
}

fn main() {
    let s = Something;
    let n = s.value();
    n.a = 43;
}
error[E0594]: cannot assign to field `n.a` of immutable binding

如果您已将值类型的所有权转让给其他人,谁在乎,如果他们更改了它?

请注意,我正在按照Growing Object-Oriented Software Guided by Tests描述的值类型进行区分,它们与对象有所区别。对象不应暴露内部。

答案 1 :(得分:5)

Rust没有提供生成吸气剂的内置方法。但是,有多个Rust功能可以用来处理样板代码!您的问题中最重要的两个:

    通过#[derive(...)]属性
  • 自定义派生
  • 例如通过macro_rules!的宏(有关如何使用它们来解决问题的信息,请参见@Shepmaster's answer

我认为避免这种样板代码的最佳方法是使用自定义派生。这使您可以为类型添加#[derive(...)]属性,并在编译时生成这些getter。

已经有一个提供以下功能的板条箱: derive-getters 。它是这样的:

#[derive(Getters)]
pub struct NamedTuple {
    a: i8,
    b: char,
}

也有getset,但有两个问题:getset的板条箱名称应为derive,但更重要的是,它鼓励“为所有内容提供者和设置者”通过提供模式也可以生成不执行任何检查的设置器。


最后,您可能需要考虑重新考虑Rust中的编程方法。老实说,根据我的经验,“获取样板”几乎不是问题。当然,有时您需要编写吸气剂,而不是“大量”的吸气剂。

在Rust中,可变性也不是唯一的。 Rust是一种多范式语言,支持多种编程风格。成语的Rust在每种情况下都使用最有用的范例。完全避免突变可能不是在Rust中编程的最佳方法。此外,避免可变性不仅是通过为您的字段提供getter来实现的,绑定和引用可变性就更为重要!

因此,对有用的字段使用只读访问权限,但不适用于所有字段。