“有限”意味着它们有一个固定的长度——也就是说,它们可以存储的值的数量是固定的。
“异构”意味着元组的元素可能具有不同的类型
1fn main() {
2 let immutable: (String, u8) = (String::from("LogRocket"), 120);
3 println!("{} {}", immutable.0, immutable.1);
4}
在上面的例子中,我们创建了一个简单的名为immutable的元组,其中第一个值是String,第二个值是8位的无符号整数。然后,我们通过从0开始按索引访问元组的元素来打印它们。运行时,示例打印“LogRocket 120”。
1fn main() {
2 let immutable: (String, u8) = (String::from("LogRocket"), 120);
3 immutable.1 = 142; // cannot assign
4}
1fn main() {
2 let mut mutable = (String::from("LogRocket"), 120);
3 mutable.1 = 142;
4 println!("{} {}", mutable.0, mutable.1);
5}
1fn main() {
2 let mut mutable = (String::from("LogRocket"), 120);
3 let (name, value) = mutable;
4 println!("{name}");
5}
上面的程序只打印“LogRocket”。
1type Test = (String, u8);
2
3fn main() {
4 let immutable: Test = (String::from("LogRocket"), 120);
5 println!("{} {}", immutable.0, immutable.1);
6}
在本节中,我们将介绍元组的两个替代方案,即结构体和元组结构体。如果元组是具有匿名字段的匿名类型,则结构体是具有命名字段的命名类型。元组结构体处于它们的中间,因为它为我们提供了带有匿名字段的命名类型。
1struct Person {
2 name: String,
3 age: u8
4}
5
6fn main() {
7 let p = Person {
8 name: String::from("Jhon Doe"),
9 age: 21
10 };
11
12 println!("Name: {}, age: {}", p.name, p.age);
13}
1struct Wrapper(u8);
2
3fn main() {
4 let w = Wrapper(10);
5 println!("{}", w.0);
6}
本节比较元组、结构体和元组结构体。首先,我们来看看几个关键的差别。然后,我们将看到它们各自的实际用例。
由于元组结构体只是没有字段名称的结构体,在本节中,我们将主要比较元组和元组结构体。我们在元组结构体中看到的也适用于结构体。事实上,元组结构体可以转化为规则结构体。
首先,元组结构体的元素在默认情况下是私有的,不能在定义它们的模块之外访问。此外,元组结构体定义了类型。因此,具有相同类型字段的两个元组结构体是两种不同的类型。
另一方面,元素类型相同的两个元组是同一个类型。换句话说,元组在结构上是等价的,而元组结构体则不是。
其次,我们可以向元组结构体添加属性,例如#[must_use](用于在某个值未被使用时发出警告)或#[derived(…)](用于在结构体中自动实现trait)。另一方面,元组不能有属性,但在默认情况下确实实现了一些特征。
1struct Name(String);
2
3fn main() {
4 let strings = [String::from("Log"), String::from("Rocket")];
5
6 let names = strings.map(Name);
7}
1struct Person(String, u8);
2
3fn main() {
4 let john = Person {
5 0: String::from("John"),
6 1: 32
7 };
8 let another_john = Person {
9 1: 25,
10 ..john
11 };
12
13 println!("name: {}, age: {}", another_john.0, another_john.1);
14}
在上面的例子中,我们首先创建了Person的一个实例,john,然后用它来创建一个新的实例,another_john,只编辑Person定义的字段的子集。
1struct Person(String, u8);
2
3impl Person {
4 fn is_adult(&self) -> bool {
5 return &self.1 >= &18;
6 }
7}
8
9fn main() {
10 let p = Person {
11 0: String::from("John"),
12 1: 20
13 };
14
15 println!("{}", p.is_adult());
16}
实际用例
根据经验,只要类型名带有语义信息,我们就应该使用元组结构体。然后,当有多个字段时,我们应该转移到结构体。
尽管如此,在某些情况下,没有名称更容易读懂。例如,slice的as_chunks方法定义如下:
1pub fn as_chunks<const N: usize>(&self) -> (&[[T; N]], &[T])
它基本上输入一个常数N,定义块的大小,并返回一个元组,其中第一个元素是大小为N的块的数组,而第二个元素是数组的余数(也就是说,最后一个不足以组成一个新块的值)。
在本例中,类型本身明确了结果的每个元素代表什么。因此,有名字可能会有点过分。在这种情况下,元组是一个很好的解决方案。
尽管如此,我们通常处理复杂的数据类型,其中有名称有助于我们阅读代码。例如,考虑一个处理图书信息的程序,特别是书名、作者和价格。如果我们用一个元组来建模,我们可能会得到:
1type Book = (String, String, f32)
1struct Book {
2 title: String,
3 author: String,
4 price: f32
5}
不过,有时我们可能会发现自己使用的结构体只有一个元素。例如,使用类型来进一步增强代码的清晰度。考虑一个用三个组件构建URL的函数:
子域
网域名称
一级域名
首先,我们可能会提出一个具有以下签名的函数:
1fn make_url(
2 subdomain: String,
3 domain_name: String,
4 top_level_domain: String
5) -> String {
6 todo!();
7}
8
9fn main() {
10 make_url(
11 String::from("www"),
12 String::from("mydomain"),
13 String::from("com")
14 );
15}
在这里,在一个结构中有很多字符串,但是,这很容易混淆参数的顺序。此外,Rust不支持命名参数,所以我们必须记住正确的顺序。
使用元组结构体,我们可以编写一个更加自解释的签名:
1struct Subdomain(String);
2struct DomainName(String);
3struct TopLevelDomain(String);
4struct URL(String);
5
6fn make_url(
7 subdomain: Subdomain,
8 domain_name: DomainName,
9 top_level_domain: TopLevelDomain
10) -> URL {
11 todo!();
12}
13
14fn main() {
15 make_url(
16 Subdomain(String::from("www")),
17 DomainName(String::from("mydomain")),
18 TopLevelDomain(String::from("com"))
19 );
20}
在上面的例子中,我们利用元组结构来包装字符串。
总结
在本文中,我们深入研究Rust中的元组。首先,我们简要地了解了它们是什么以及如何使用它们。其次,我们简要介绍了可能的替代,即结构体和元组结构体。最后,我们查看比较了实现细节以及每个选项的实际用例。真正重要的选择是它们如何影响代码的可读性和可维护性。
本文翻译自:
https://blog.logrocket.com/practical-use-cases-rust-tuples/