Rust 數組、向量和切片

簡介

在這篇文章中,我將介紹 Rust 中的數組、向量和切片。來自 C 或 c++ 的程序員已經熟悉了數組和向量,但是因爲 Rust 關注的是安全性,所以它與不安全的語言是有一些區別的。另一方面,切片將是一個全新的,非常有用的概念。

數組

數組是在連續內存塊中分配的相同類型元素的集合, 例如,如果你像這樣分配一個數組:

let array: [i32; 4] = [42, 10, 5, 2];

然後所有的 i32 整數在堆棧上挨個分配:

在 Rust 中,數組的大小也是類型的一部分。例如,以下代碼將無法編譯:

//error:expected an array with a fixed size of 4 elements,
//found one with 3 elements
let array: [i32; 4] = [0, 1, 2];

向量

數組的最大限制是它們的大小是固定的。相反,向量可以在運行時增長:

fn main() {
    //There are three elements in the vector initially
    let mut v: Vec<i32> = vec![1, 2, 3];
    //prints 3
    println!("v has {} elements", v.len());
    //but you can add more at runtime
    v.push(4);
    v.push(5);
    //prints 5
    println!("v has {} elements", v.len());
}

一個向量如何實現動態增長?在內部,vector 將所有元素分配到堆上的一個數組中,當 push 一個新元素時,vector 檢查數組中是否還有剩餘的容量。如果沒有,vector 分配一個更大的數組,將所有元素複製到新數組,並釋放舊數組。這可以在下面的代碼中看到:

fn main() {
    let mut v: Vec<i32> = vec![1, 2, 3, 4];
    //prints 4
    println!("v's capacity is {}", v.capacity());
    println!("Address of v's first element: {:p}", &v[0]);//{:p} prints the address
    v.push(5);
    //prints 8
    println!("v's capacity is {}", v.capacity());
    println!("Address of v's first element: {:p}", &v[0]);
}

最初 v 的容量是 4:

然後將一個新元素壓入向量。這使得 vector 將所有元素複製到容量爲 8 的新的數組中:

程序還打印數組中第一個元素在將新元素壓入 vector 容器之前和之後的地址。這兩個打印出來的地址是不一樣的。地址的這種變化清楚地表明,分配了一個大小爲 8 的新數組。

切片

切片就像數組或向量的臨時視圖。例如,如果有一個數組:

let arr: [i32; 4] = [10, 20, 30, 40];

你可以像這樣創建一個包含第二個和第三個元素的切片:

let s = &arr[1..3];

[1..3]語法創建從索引 1(包括)到索引 3(不包括)的範圍。如果你省略了範圍中的第一個數字 ([..3]),它默認爲零,如果你省略了最後一個數字([1..]),它默認爲數組的長度。如果輸出[1..3] 中的元素,你得到 20 和 30:

/prints 20
println!("First element in slice: {:}", s[0]);
//prints 30
println!("Second element in slice: {:}", s[1]);

但是,如果試圖訪問切片範圍之外的元素,它就會出現 panic:

//panics: index out of bounds
println!("Third element in slice: {:}", s[2]);

但是切片怎麼知道它只有兩個元素呢?這是因爲 slice 不僅僅是一個指向數組的指針,它還有一個額外的長度字段記錄了 slice 元素的數量。

注意:除了指向對象的地址之外,還有其他數據的指針稱爲胖指針。在 Rust 中,切片並不是唯一的一種胖指針。例如,Trait Object 除了攜帶指向對象的指針外,還攜帶一個指向虛表的指針。

如果你爲向量創建一個切片:

let v: Vec<i32> = vec![1, 2, 3, 4];
let s = &v[1..3];

s 除了指向 v 緩衝區中的第二個元素的指針,s 還有一個值爲 2 的 8 字節長度的字段:

長度字段的存在也可以在下面的代碼中看到,其中 slice(&[i32]) 的大小爲 16 字節 (緩衝區指針爲 8 字節,長度字段爲 8 字節):

use std::mem::size_of;
fn main() {
    //prints 8
    println!("Size of a reference to an i32: {:}", size_of::<&i32>());
    //print 16
    println!("Size of a slice: {:}", size_of::<&[i32]>());
}

由於切片借用底層數據結構,所以適用所有常用的借用規則。例如,這段代碼會被編譯器拒絕:

fn main() {
    let mut v: Vec<i32> = vec![1, 2, 3, 4];
    let s = &v[..];
    v.push(5);
    println!("First element in slice: {:}", s[0]);
}

爲什麼?因爲當切片被創建時,它指向 vector 的支持緩衝區的第一個元素,當一個新元素被壓入 vector 時,它分配一個新的緩衝區,而舊的緩衝區被釋放。這會使切片指向一個無效的內存地址,如果訪問該地址將導致未定義的行爲。

注意:由於切片可以由數組和向量創建,因此它們是一種非常強大的抽象。因此,對於函數中的參數,默認的選擇應該是接受一個切片,而不是數組或向量。事實上,許多函數,如 len, is_empty 等,都接受切片,而不是向量或數組。

本文翻譯自:

https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/

coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BJs-uA3m3o7c8BZ-s2l9pQ