使用 元组结构体,当:
你会发现这种情况并不经常出现。如果是的话,通常只是尝试将一个完全相同的其他类型包装成一个新类型来提供不同的行为。这是一种已知的模式,称为'newtype' pattern。当你在 struct 中有多个字段时,通常需要为它们命名。
快速提醒:
让我们来看看[T]::split_at()
:
fn split_at(&self, mid: usize) -> (&[T], &[T])
SplitSlice
, SliceParts
, ...? 无论我们给出的名称是什么,都是多余的,因为函数已经被适当地命名了。left
和 right
会使每个部分更加清晰明确。但是这里我们假设程序员有正确的直觉。英语是从左到右写的,在英语文化中,数组通常从左到右绘制([0 | 1 | 2 | 3 ]
),所以对大多数人来说,这是有意义的。⇒ 只返回元组就可以了!
fn find_first_occurence(file: &TextFile, needle: &str) -> (usize, usize)
返回类型能够清晰地表达吗?并不是...即使你知道文件中的区域是由字节偏移量指定的,返回值仍然是模棱两可的:它可以是(start, end)
,也可以是(start, stuff)
,其中stuff可以是任何其他搜索度量(函数不需要返回end
,因为我们已经知道needle
的长度,因此可以计算出它)。因此,我希望您同意,我们要给返回类型命名。让我们称之为Span
——这是 Rust编译器中使用的名称。
下一个问题:结构体或元组结构体?是否有意义为字段命名?同样,没有明确的答案,但我认为我们应该为字段命名。哪个更容易阅读:span.1 - span.0
还是span.high - span.low
?此外,我们可以为命名字段编写文档;例如,记录high
是排除性的。
⇒ 结构体
fn get_line_number(file: &TextFile, span: Span) -> ???
u32
可能就足够了!这个u32
代表的含义毫无疑问。不过:行号是从0还是1开始计数呢?叹气
当然,我们可以在函数上记录此属性...以及与行号相关的每个其他函数。那么,在创建一个新类型并在其中记录它的方式如何?这也将有助于使用多个数字(包括行号)的函数:print_snippet(&file, 57, 63, 80);
等等,现在什么是行号?确切地说:它将使用LineNumber
而不是u32
。类型系统就是文档。
我们现在同意创建一个新类型。但是:结构体还是元组结构体?让我们尝试结构体:
struct LineNumber {
line_number: u32, // uhm...
}
好的,如何称呼该字段?唯一适合的名称分为两类:
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)
}
}
usize
。元组结构不太常见。当您只有少量成员,而且很清楚哪些成员是什么,不需要名称时,请使用它们。
元组结构的一个常见用途是新类型。这是一个只有一个成员的元组结构。这对于在现有类型周围创建简单的包装器非常有用。
元组和元组结构体的主要区别在于后者引入了一个名称,而前者没有。
有时,数据被捆绑在一起只是因为需要。例如,想象一个状态机:每个转换都返回一个数据片段以及状态机的下一个状态,这叫什么名字?
impl StateA {
fn on_event_x(self) -> (String, StateB);
}
这种情况不应该被滥用;毕竟,命名在文档 API 中很有用!然而,有时候就是找不到一个合适的愚蠢名称(StringAndStateB
:x),这时使用元组结构体是比较好的选择。
那么,什么时候使用元组结构体呢?当您需要或希望为类型命名,并且您更喜欢使用元组结构体而不是常规结构体或枚举(这是另一个完全不同的讨论!)时。
免责声明:我是 Rust 新手。
对于私有方法,你应该知道传递的内容,所以元组是可以的,如果需要的话,你总是可以随后更改它。
对于公共方法,避免歧义更为重要。例如,假设我们有元组 (u8, u8, u8, u8)
来表示颜色;它是 RGBA、ARGB、BGRA、ABGR 还是 HSLA?
将其表示为元组结构体 Rgba(u8, u8, u8, u8)
会更清晰。
此外,请注意,如果你从公共函数或公共 trait 中公开一个元组,则在未来限制了扩展性,因为引入任何添加或更改都会破坏已定义的 API。
使用结构体或元组结构体,你可以将方法添加到结构体中。
例如:
struct Rgba(u8, u8, u8, u8);
impl Rgba {
pub fm as_hsl() -> Hsla(u8, u8, u8, u8) { todo!(); }
pub fm lighten(&mut self, u8) { todo!(); }
pub fm darken(&mut self, u8) { todo!(); }
}