Rust 入门实战系列(5)- 复合类型

1,249 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情

今天我们接着昨天的 标量 4 种数据类型继续 学习 Rust 提供的原生的复合类型(Compound) 。

Rust 提供的复合类型一共有两种: tuple(元组),array(数组)。

Tuple

Rust 的 tuple 可以将一系列不同类型的值组装到一起,变成一个复合类型的值。有过 Python 开发经验的同学对 tuple 会比较熟悉。

tuple 的长度是固定的,一旦声明,就不能再扩容/缩容。声明一个 tuple 很简单:

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

我们通过 (500, 6.4, 1) 这样的逗号分隔格式就可以创建一个 tuple。注意到我们这里有三个元素,每个元素都有自己的类型,它们不必非得一样。这里我们还加上了完整的类型声明,如果不加编译器会自动推断。

事实上这样也是可以的:

let tup = (500, 6.4, 1);

上面我们声明的 tup 变量是和整个 tuple 绑定的,它们是一个整体。我们当然也可以获取 tuple 中的值,这里有两种做法:

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {y}");
}

上面这种是直接赋值,变量长度和 tup 长度匹配即可。

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

第二种方法则是用 . + index 来直接根据序号获取,上面这个例子里,想获取 x 这个 tuple 的第一个值,我们就可以用 x.0 (index 都是从 0 开始的,这个大家很熟悉)。

需要特别提一下的是,我们也可以声明一个完全不包含任何值的 tuple,这类 tuple 有个专用的名字:unit。类似这样:let _u = ();。目前大家记住有这个概念即可,后面的章节我们会看到它是怎么用到的。

array

array 是一个定长的数组,所有元素的类型必须一致,这也是跟 tuple 最大的区别。

现代高级语言一般都会提供两个类型给开发者选择,一个是定长的数组,一个是可扩容/缩容的数组,后者效率不如前者高,但覆盖场景更多。比如在 Golang 中的 array 和 slice。

在 Rust 中,这个可扩容/缩容的数组叫做 vector,同样是由标准库提供的。我们会在之后的章节介绍。

array 这种定长的数组的效率更高,如果你知道这个数组内的元素数量不会变,用 array 就是最佳选择,例如存储一年的月份,可以这样:

let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];

当然,在需要类型的时候,我们可以用下面的形式来声明:

let a: [i32; 5] = [1, 2, 3, 4, 5];

方括号里分号前面的部分是类型 i32,后一部分指的是我们的 array 包含 5 个元素。

还有些时候我们希望声明一个所有元素的值都相同的数组,可以这样:

let a = [3; 5];

一定要分清这两个,前面那个 [i32; 5] 是在类型的部分,这里的 [3; 5] 是在值的部分,意味着我希望申请一个 5 个元素长度的数组,所有值都是 3。

等价于这样的写法:

let a = [3, 3, 3, 3, 3];

访问数组元素

array 还有一个很重要的属性:所有的数据都在 栈(stack)上,它是一段连续的,固定长度的内存。

我们访问 array 中某个元素的方式和 tuple 略有区别。

  • tuple
 let x: (i32, f64, u8) = (500, 6.4, 1);
 let five_hundred = x.0;
  • array
 let a = [1, 2, 3, 4, 5];

 let first = a[0];
 let second = a[1];

array 中我们用方括号 + 下标来访问元素。

非法访问

如果 Rust 中你访问了超出数组长度的下标,会怎么样?我们将 guess number 中的示例稍微改一下(代码应该很好理解了,如果不熟悉的同学建议回看一下咱们这个系列的 2,3 两篇文章)

use std::io;

fn main() {
    let a = [1, 2, 3, 4, 5];

    println!("Please enter an array index.");

    let mut index = String::new();

    io::stdin()
        .read_line(&mut index)
        .expect("Failed to read line");

    let index: usize = index
        .trim()
        .parse()
        .expect("Index entered was not a number");

    let element = a[index];

    println!("The value of the element at index {index} is: {element}");
}

这段代码本身有没有语法问题?

没有,编译是能过的,说白了问题在于 index 是从外部输入的,当你输入 0,1,2,3,4 这几个小于等于数组长度的下标时,没问题,能取到数据。

但是,如果你输入了个 10 呢?

$ cargo run

================================

    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/compound`
Please enter an array index.
10
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:33:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

出现了 runtime error,程序还是 panic 了,提示数组下标越界,后面的 println 没有执行,整体程序就退出了。

这一点也是符合 Rust 对于内存安全的原则,如果不检查下标,允许你访问了,就会访问到别的变量数据,这一定是不符合预期的。

基本数据类型示例

到这里,我们就把 4 种标量(整型,浮点型,布尔,字符),2种复合类型(元组,数组)学完了。其实 Rust 提供的数据类型还很多。这里我们学到的 6 种是最基础的 Primitives。在此之上还可以封装出来各种各样的数据结构。

文章的最后,我们参考 Rust 的 example 看一个例子,把我们这两篇文章的精华用代码注释来串一下:

fn main() {
    // Variables can be type annotated.
    let logical: bool = true;

    let a_float: f64 = 1.0;  // Regular annotation
    let an_integer   = 5i32; // Suffix annotation

    // Or a default will be used.
    let default_float   = 3.0; // `f64`
    let default_integer = 7;   // `i32`
    
    // A type can also be inferred from context 
    let mut inferred_type = 12; // Type i64 is inferred from another line
    inferred_type = 4294967296i64;
    
    // A mutable variable's value can be changed.
    let mut mutable = 12; // Mutable `i32`
    mutable = 21;
    
    // Error! The type of a variable can't be changed.
    mutable = true;
    
    // Variables can be overwritten with shadowing.
    let mutable = true;
}

感谢阅读!欢迎评论区交流。