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

war*_*ock 5 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

She*_*ter 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());
}
Run Code Online (Sandbox Code Playgroud)

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

我要挑战你拥有和你想象的一样多的样板.例如,您只显示Copy类型.一旦你添加了一个String或一个Vec结构,这将崩溃,你需要返回一个引用或采取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);
}
Run Code Online (Sandbox Code Playgroud)

现有的Rust功能可以防止getter方法首先尝试解决的大多数问题.

基于变量绑定的可变性

n.a = 43;
Run Code Online (Sandbox Code Playgroud)
error[E0594]: cannot assign to field `n.a` of immutable binding
Run Code Online (Sandbox Code Playgroud)

参考规则

struct Something;

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

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

如果您已将值类型的所有权转让给其他人,谁会关心他们是否更改了?

请注意,我正在对由测试引导的面向对象的增长软件所描述的值类型进行区分,它们与对象区分开来.对象不应该有暴露的内部.

  • `Cell`更像是C++中的`mutable`关键字,很少使用.你不要只使用它来改变成员:你使用它,这样即使对象是`const`*,也可以改变成员*.在C++中,你只能将它用于memoization之类的东西,所以你可以从`const`成员函数中缓存一些值,而Rust中的用例大致相似.我不认为C#有同等的? (2认同)
  • @warlock我可以看到你来自哪里,但我认为你忽略了`&mut`引用的*唯一性*,不止一种意义上说.C#,MATLAB,Python,它们都没有等同于`&mut`.C在技术上有`restrict`,但它的功能非常有限,很少在API中找到(并且它不允许你修改`const`成员).而实际上每个Rust API都会使用`&mut`*.这是有道理的 - "&mut"引用允许变异,但它们也不允许别名,它排除了99%的错误,你会试图用C#中的`readonly`来避免. (2认同)

Luk*_*odt 5

Rust不提供生成getter的内置方法.但是,有多个Rust功能可用于处理样板代码!对您的问题最重要的两个:

  • 通过#[derive(...)]属性自定义派生
  • 通过示例通过宏macro_rules!(请参阅@ Shepmaster关于如何使用它们来解决问题的答案)

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

已经有一个箱子提供了这个:derive-getters.它的工作原理如下:

#[derive(Getters)]
pub struct NamedTuple {
    a: i8,
    b: char,
}
Run Code Online (Sandbox Code Playgroud)

还有getset,但它有两个问题:getset应该有derive它的箱子名称,但更重要的是,它通过提供也生成不执行任何检查的设置器来鼓励"一切的吸气剂和安装者"反模式.


最后,您可能需要考虑重新考虑Rust中的编程方法.老实说,根据我的经验,"getter样板"几乎不是问题.当然,有时候你需要编写getter,而不是"大量"的getter.

可变性在Rust中也不是单一的.Rust是一种多范式语言,支持多种编程风格.Idiomatic Rust为每种情况使用最有用的范例.完全避免变异可能不是Ru​​st中编程的最佳方式.此外,避免可变性不仅通过为您的领域提供getter来实现 - 绑定和引用可变性更为重要!

因此,对只有它有用的字段使用只读访问权限,但不能到处使用.