通常情况下,你的程序必须处理多个数据实例,因此,可使用集合类型。根据你的需要以及数据驻留在内存中的位置,Rust 提供了多种内置类型来储存数据集合。首先我们有数组和元组。然后我们的标准库中有动态集合类型,将介绍其中最常用的类型,它们基本上是对某些其他变量所有的连续数据的视图。

数组

数组具有固定长度,可以储存相同类型的元素。它们用 [T, N]表示,其中 T 表示任意类型,N表示数组元素数量。数组的大小不能用变量表示,并且必须是 usize 的字面值:

  1. fn main() {
  2. let numbers: [u8;10] = [1,2,3,4,5,6,7,8,9,10];
  3. let floats = [0.1f64,0.2,0.3];
  4. println!("Number: {}",numbers[5]);
  5. println!("Float: {}",floats[2]);
  6. }
  1. 上述代码中,我们声明了一个整形数组,其中包含`10`个元素,并在左侧指定了元素的类型。在第二个浮点型数组中,我们将类型指定为数组中第一个元素的后缀,即 `0.1f64`.这是指定类型的另一种方法。

元组

元组与数组的不同之处在于,数组的元素必须具有相同的类型,而元组中的元素可以具有不同的类型。元组是异构集合,可用与将不同类型的元素储存在一起,从函数返回多个值时可以使用它。

  1. fn main() {
  2. let num_and_str: (u8, &str) = (40, "Hava a good day!");
  3. println!("{:?}",num_and_str);
  4. let (num, string) = num_and_str;
  5. println!("From tuple: Number: {},String: {}",num,string);
  6. }

在上述代码中,num_and_str 是一个包含两个元素的元组,即(u8, &str)。我们还将已经 声明的元组中的值提取到单个变量中。输出元组后,我们将已经声明的元组解构为 numstring 变量,并自动推断它们的类型。该代码非常简洁。

向量

向量和数组类似,不过它们的内容和长度不需要事先指定,并且可以按需增长。

它们是在堆上分分配的。我们既可以使用构造函数 Vec::new,也可以使用宏vec![]创建它们:

  1. fn main() {
  2. let mut numbers_vec: Vec<u8> = Vec::new();
  3. numbers_vec.push(1);
  4. numbers_vec.push(2);
  5. let mut vec_with_macro = vec![1];
  6. vec_with_macro.push(2);
  7. let _ = vec_with_macro.pop();//忽略空格
  8. let message = if numbers_vec == vec_with_macro {
  9. "They are equal"
  10. }else {
  11. "Nah! They look different to me"
  12. };
  13. println!("{} {:?} {:?}",message,numbers_vec,vec_with_macro);
  14. }

在上述代码中,我们以不同方式创建了两个向量,即 numbers_vecvec_with_macro。 我们可以使用 push()方法将元素推送到 vector 中,并可以使用 pop()方法删除元素。如果你 希望了解更多相关的方法,可以参考官方帮助文档,还可以使用 for 循环语句迭代访问 vector,因为它们也实现了 Iterator 特征。

键/值对

Rust 还为我们提供了 键/值对,它可以用于存储键/值对。它们来自std::collections模块,名为 HashMap。它们是使用构造函数HashMap::new创建的:

  1. use std::collections::HashMap;
  2. fn main() {
  3. let mut fruits = HashMap::new();
  4. fruits.insert("apple",3);
  5. fruits.insert("mango",6);
  6. fruits.insert("orange",2);
  7. fruits.insert("avocado",7);
  8. for (k , v) in &fruits {
  9. println!("I got {} {}", v, k);
  10. }
  11. fruits.remove("orange");
  12. let old_avocado = fruits["avocado"];
  13. fruits.insert("avocado",old_avocado + 5);
  14. println!("\nI now have {0} avocados{0}",fruits["avocado"]);
  15. }

上述代码中,我们新建了一个名为 fruitsHashMap。然后使用insert方法向其中插入了一些水果元素以及相关的计数。接下来,我们使用for循环遍历 键/值对,其中通过&fruits 引用我们的水果映射结构,因为我们只希望读取其中的键和值。默认情况下,for循环将使用该值。

在上述情况下,for循环返回一个包含两个字段的元组(k, v)。还有单独的方法keys()value()分别用于迭代访问键和值。用于哈希化HashMap类型键的哈希算法基于Robin hood开放寻址方案,但我们可以根据用例和性能替换成自定义哈希方案。

切片

切片是获取集合类型视图的常用做法。大多数用例是对集合类型中特定区间的元素进行只读访问。切片基本上是指针或引用,指向现有集合类型中某个其他变量所拥有的连续区间。实际上,切片是指向堆栈或堆中某处现有数据的胖指针,这意味着它还包含关于指向元素多少的信息,以及指向数据的指针。

切片用&[T]表示,其中 T 表示任意类型。它们的使用方式与数组非常类似:

  1. fn main() {
  2. let mut numbers: [u8; 4] = [1,2,3,4];
  3. {
  4. let all: &[u8] = &numbers[..];
  5. println!("All of them: {:?}",all);
  6. }
  7. {
  8. let first_two: &mut [u8] = &mut numbers[0..2];
  9. first_two[0] = 100;
  10. first_two[1] = 99;
  11. }
  12. println!("Look ma! Ican modify through slices: {:?}",numbers);
  13. }

上述代码中有一个 numbers数组,这是一个堆栈分配值。然后我们使用&numbers[..]语法对数组中的数字进行切片并储存到变量all中,其类型为&[u8]。末尾的[..]表示我们要获取整个集合。这里我们需要用到&,是因为切片是不定长类型(unsized type),不能将切片储存为裸值—即仅在指针后面。

  • **&[u8]** 表示 **all** 是一个指向 **u8** 类型元素的不可变切片。
  • **<font style="color:rgb(13, 13, 13);">&mut numbers[0..2]</font>**<font style="color:rgb(13, 13, 13);"> </font>表示创建一个从索引 **<font style="color:rgb(13, 13, 13);">0</font>****<font style="color:rgb(13, 13, 13);">2</font>**(不包括 **<font style="color:rgb(13, 13, 13);">2</font>**)的切片,它是可变的,所以我们可以修改这些元素。
  • **&mut [u8]**<font style="color:rgb(13, 13, 13);"> </font>表示 **first_two** 是一个指向 **u8** 类型元素的可变切片。

我们还可以提供范围([0..2])以获得任意区间的切片。切片也可以,可变地获得。first_two是一个可变切片,我们可以通过它修改原是的number数组。

:::info 注意

&str 类型也属于切片类型([u8]),与其他字节切片的 唯一区别在于,它们保证为 UTF-8。也可以在 VecString 上执行切片。

:::