数组 Array

数组 (array), 用于存在固定长度的相同数据类型的列表.

#![allow(unused)]
fn main() {
let arr: [i32; 4] = [1, 1, 2, 3];
}

其类型声明可以写成:

pub use type Array<T, N> = [T; N];

数组内存是分配在栈空间的, 内存是连续分配的, 它的类型及大小是在编译期间就确定的.

[T; N] 在编译期确定元素类型及个数, 且元素个数不可变; 另外, 数组在编译期就需要初始化.

有两种方法来创建数组:

  • 可以显式地指定所有元素的值, let arr = [1, 2, 3, 4, 5];
  • 可以一次性初始化成相同的值, let arr = [42; 100]; 会创建有100个元素的数组, 元素的值都是42

看下面的一个示例程序, 用于计算 10000 以内的所有质数:

// See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
fn main() {
    const MAX_NUM: usize = 10_000;
    const NUM_SQUARED: usize = 100;
    let mut primes: [bool; MAX_NUM] = [true; MAX_NUM];
    primes[0] = false;
    primes[1] = false;
    for i in 2..=NUM_SQUARED {
        if primes[i] {
            for j in ((i * i)..MAX_NUM).step_by(i) {
                primes[j] = false;
            }
        }
    }

    println!("primes <= {MAX_NUM}: [");
    let mut count: i32 = 0;
    for (index, is_prime) in primes.iter().enumerate() {
        if *is_prime {
            print!("{index}, ");
            count += 1;
        }
        if count == 10 {
            println!();
            count = 0;
        }
    }
    println!("]");
}

数组的内存布局

以下面的代码片段作为示例:

use std::mem::size_of_val;
use std::ptr;

fn main() {
    let local_start: i32 = 0x1234;
    let arr: [i32; 6] = [1, 1, 2, 3, 5, 8];
    let addr: *const [i32; 6] = ptr::addr_of!(arr);
    let arr_ref: &[i32] = arr.as_slice();
    let addr2: *const i32 = arr.as_ptr();
    let local_end: i32 = 0x5678;
    assert_eq!(size_of_val(&arr), 24);
    assert_eq!(addr as *const (), addr2 as *const ());
    assert_eq!(arr_ref.as_ptr(), addr2);
    assert!(local_start < local_end);
}

在调试器里查看 arr 的内存, 结果如下图:

array size

看内存里的内容, 可以发现 arr 确实是一个存储相同元素大小 (i32) 连续内存块, 其占用的内存大小为 4 * 6 = 24 24个字节.

其它几个变量都是指针类型, 但里面的指针都指向的是 arr 的内存地址:

  • addr, 直接调用 addr_of!() 宏, 返回对象的内存地址, 它不需要创建临时对象
  • arr_ref, 是一个胖指针 (fat pointer), 是一个切片引用 &[T], 除了包含 buffer 地址之外, 还存储了切片中元素的个数, 为6个
  • addr2, 通过调用 slice::as_ptr() 方法, 创建一个切片临时对象, 并返回切片的 buffer 地址

把上面的内存块经过抽像处理后, 可以得到各变量的内存布局图:

array mem layout

另外, arr 直接存储在栈内存. 所以数组占用的空间不能太大, 否则会出现 stack overflow 问题, linux 平台线程的栈内存默认只有 8MB 的空间:

fn main() {
    const ARR_LEN: usize = 1_000_000_000;
    let arr = [0i32; ARR_LEN];
    println!("arr length: {}", arr.len());
}

这个程序会运行失败, 输出如下错误:

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Aborted (core dumped)

数组的常用方法

数组的操作方法, 比如 arr.len(), 都是隐式地将它先转换成相应的 切片 slice, 再调用切片提供的方法.

  • as_slice(), as_mut_slice(), 显式地转换成切片 ([T]), 这样就可以调用切片的方法
  • each_ref(), each_mut(), 转换成新的数组, 新数组中每个元素的值是对当前数组中同一个位置元素的引用
fn main() {
    let mut distro_list: [String; 3] = [
        "Debian".to_owned(),
        "Ubuntu".to_owned(),
        "Fedora".to_owned(),
    ];

    let distro_ref: [&mut String; 3] = distro_list.each_mut();
    for entry in distro_ref {
        entry.push_str(" Linux");
    }

    println!("distro list: {distro_list:?}");
}

参考