什么时候我应该使用元组结构超过正常元组?

Cha*_*dan 5 types rust

在Rust Programming Language一书中,在Structs一章中我们介绍了元组结构.

在哪些情况下我应该使用元组结构而不是正常的元组(除了书中提到的例子)?

Luk*_*odt 13

摘要

在以下情况下使用元组结构:

  • 类型的名称带有语义信息
  • 命名字段不会添加任何语义信息

你会发现这种情况并非经常发生.如果是,你通常只是试图总结正好一个其他类型的到一个新的类型,给它不同的行为.这是一种已知的模式,称为"新类型"模式.如果结构中有多个字段,通常需要为它们命名.


例子

作为快速提醒:

  • 元组是具有匿名字段的匿名类型
  • 元组结构是具有匿名字段的命名类型
  • 结构体是具有命名字段的命名类型

我们来看看[T]::split_at():

fn split_at(&self, mid: usize) -> (&[T], &[T])
Run Code Online (Sandbox Code Playgroud)

他们这个函数的作者选择在这里返回一个元组.让我们看看,我们会从...获得任何东西吗?

  • 命名类型:不是,对吧?我们称之为什么?SplitSlice,SliceParts,...?我们可以给出的每个名字都是多余的,因为该功能已经恰当地命名.
  • 命名字段:有问题.left并且right会更清楚地说明哪一方是哪一方.但在这里我们假设程序员有正确的直觉.英语是从左到右书写的,在英语文化中,数组通常是从左到右绘制的([0 | 1 | 2 | 3 ]),因此对大多数人来说都是有意义的.

⇒只是一个元组很好!


另一个例子:假设你正在编写一个以某种方式与文本文件(编译器,文本编辑器......)一起工作的应用程序.当您在讨论文本文件中的某个区域(例如,搜索结果)时,您希望通过给出字节偏移来指定该区域.让我们看看元组是否适合我们:

fn find_first_occurence(file: &TextFile, needle: &str) -> (usize, usize)
Run Code Online (Sandbox Code Playgroud)

返回类型是否适合自己?而不是......即使你知道文件中的区域是由字节偏移指定的,返回值仍然是不明确的:要么是(start, end)它的(start, stuff),要么是东西可以是任何其他搜索指标(函数不需要返回end,因为我们已经知道它的长度,needle因此可以计算它).所以,我希望你同意,我们想要命名返回类型.我们称之为Span- 这是Rust编译器中使用的名称.

下一个问题:struct或tuple struct?命名字段是否有意义?同样,没有明确的答案,但我认为我们确实想要命名字段.什么更容易阅读:span.1 - span.0span.high - span.low?另外,我们可以为命名字段编写文档; 例如,记录这high是独占的.

⇒结构


现在想象您想要向用户报告行号.特别重要的是返回给定跨度的相应行号的函数(为简单起见,我们假设此跨度从不跨越多行).

fn get_line_number(file: &TextFile, span: Span) -> ???
Run Code Online (Sandbox Code Playgroud)

那么我们又回来了什么?在这个函数的上下文中,一个简单u32的可能就好了!毫无疑问,这u32代表了什么.虽然:行号计数是从0还是1开始?

当然,我们可以在函数上记录这个属性......并且每个其他函数都使用行号.那么如何创建一个新类型并将其记录在那里呢?这也有助于获取多个数字的函数,包括行号:

print_snippet(&file, 57, 63, 80);
Run Code Online (Sandbox Code Playgroud)

等等,现在的行号是多少?确切地说:不是采用u32s,而是采用LineNumbers - 类型系统是文档.

我们现在同意创建一种新类型.但是:struct或tuple struct?让我们试试struct:

struct LineNumber {
    line_number: u32,    // uhm...
}
Run Code Online (Sandbox Code Playgroud)

那么,怎么称呼这个领域?唯一合适的名称分为两类:

  • 如结构名称:line_number,number,line,...
  • 无语义信息:inner,value,data,...

命名该领域并没有什么好处.所以,让我们不要使用......

⇒元组结构

使它成为一个自己的类型的决定有一些很好的结果:我们可以在我们的源代码中使用基于0的数字,但永远不必担心错误地打印它:

impl fmt::Display for LineNumber {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        (self.0 + 1).fmt(f)
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以在一个地方将基于0的数字调整为基于1的数字(对于那些该死的人类!)!

另请注意,我们正在谈论上面的字节偏移.而不是使用usize,我们还应该创建一个新类型,以区别于char偏移,例如!

  • 再想一想,对于本文,博客帖子比SO答案更合适\*\_\** (2认同)
  • 天啊!感谢您花时间写出如此全面的答案:) (2认同)