Rust 勸退系列 05:複合數據類型
閱讀本文大概需要 8 分鐘。
大家好,我是站長 polarisxu。
這是 Rust 勸退系列的第 5 個教程,探討 Rust 中的複合數據類型(Compound types)。Rust 中有兩種原生的複合類型:元組(tuple)和數組(array),順帶介紹切片。
01 元組類型
Go 語言沒有元組類型,但多返回值有點類似元組(但還是有區別的哦)。Python 中有元組類型,因此如果你熟悉 Python,對元組應該很熟悉。
什麼是元組類型?
元組是一個可以包含各種類型的值的組合。元組是一個將多個其他類型的值組合進一個複合類型的主要方式。元組長度固定:一旦聲明,其長度無法增大或縮小。元組的類型由各組成元素類型的序列定義。
元組通過小括號定義,裏面的元素通過逗號分隔,例如:
(23.2, 27, 'a');
這個字面值元組的類型是:(f64, i32, char),即對應每個元素的默認類型。因此,我們可以通過 let 將這個元組綁定到變量上,Rust 會進行類型推斷:
let tup = (23.2, 27, 'a');
在 VSCode 中可以看到 tup 的類型就是:(f64, i32, char)。同樣地,我們也可以爲 tup 使用類型註解:
let tup: (f32, i8, char) = (23.2, 27, 'a');
因爲元組是多個類型的集合,對元組中的類型沒有限制。因此,可以嵌套。比如:
(2, (2.1, 'a'), false);
不過建議別嵌套太多,否則可讀性太差。
如何訪問元組元素呢?
上面說,Go 語言中函數多返回值類似元組,在接收多返回值時,通過多個變量接收,比如:
// Go 語言
f, err := os.Open("abc.txt")
在 Rust 中,可以解構元組(這也叫模式匹配解構):
let tup = (23.2, 27, 'a');
let (x, y, z) = tup; // 注意:需要小括號
和 Go 語言一樣,如果某個元素我們不關心,可以放入垃圾桶(_
):
let tup = (23.2, 27, 'a');
let (x, _, z) = tup; // 注意:需要小括號
Rust 中變量定義未使用,不會像 Go 一樣報錯,但會警告!
除了模式匹配解構,還可以使用類似訪問數組元素的方式訪問元組元素,只不過不是用[]
,而是用 .
加索引的方式(索引也是從 0 開始):
let tup = (23.2, 27, 'a');
println!("{}", tup.1); // 輸出:27
特殊的元組
當元組中只有一個元素時(即元組長度是 1),唯一的元素後面必須加上逗號:
let tup = (2,); // 逗號不能少,否則會提示你,單個值應該去掉小括號。這是避免將小括號當做計算的優先級
自然,模式匹配解構元組時,也必須有逗號。
如果元組沒有元素呢?即空元組。看下面的代碼:
fn main() {
let result = test_tuple();
println!("{:?}", result);
}
fn test_tuple() {
println!("test empty tuple");
}
你猜打印 result 是啥?
擦,竟然是 ()
,即空元組。而且 Rust 給它專門取了一個名字:單元類型(unit type),也就是說,()
叫單元類型,它有一個唯一值:空元組 ()
。而且,因爲沒有任何元素,Rust 將其歸爲變量類型。
還嫌 Rust 不夠複雜嗎?就叫空元組不行嗎?非得搞一個單元類型,這麼奇怪的類型。。。
爲了避免複雜性,我覺得大家將其理解爲空元組即可。至於爲什麼這裏會返回空元組,在函數部分會講解。
注意:() 是不佔空間的,這和 Go 中的空結構體類似。
02 數組
Rust 中的數組和 Go 中的類似,是不可變的,由元素類型和長度確定,且長度必須是編譯期常量。Rust 中,數組類型標記爲 [T; size]
。數組字面值使用 []
表示:
let a = [1, 2, 3, 4];
同樣會進行類型推斷(包括長度)(這裏推斷出 a 的類型是 [i32; 4]
),也可以顯示進行類型註解:
let a: [i8; 4] = [1, 2, 3, 4];
相比較而言,Rust 創建數組比 Go 簡單,它和 PHP 這樣的動態語言類似。在 Go 中一般這樣創建數組:
// Go 語言
a := [...]int{1, 2, 3, 4}
也就是說,Go 中創建數組是,類型信息不能少,沒法跟 Rust 一樣進行類型推斷。
除了上面的初始化方法,Rust 中還可以這樣簡單的初始化:
let a = [-1; 4]; // 4 個元素都是 -1
Rust 變量必須初始化後才能使用,而 Go 語言中,變量會有默認值。所以,Go 中可以簡單的定義一個數組,然後使用默認的初始值。如:
// Go 語言
var a [4]int // a 的值是:[0 0 0 0]
此外,Rust 中數組總是分配在棧中的,因此可以認爲數組是「值類型」,和 Go 一樣,我們不應該直接傳遞數組,而應該和 Go 一樣,使用 slice。
03 切片(slice)
Rust 中的切片和 Go 中的切片意思一樣,表示對數組部分元素的引用。但和 Go 不同的是,Rust 的切片沒有容量的概念,只有一個指向數據的指針和切片的長度。Rust 中切片的類型標記爲 &[T],即對數組進行引用(&)就是切片。
Go 語言中有直接創建切片的語法(比如 make),但 Rust 中沒有,它必須依賴數組或 Vec(以後講解),通過引用來創建。
let xs = [1, 2, 3, 4, 5];
let slice = &xs;
既然切片是數組元素的片段引用,那如何引用部分片段呢?
在 Go 中是這麼做的:
var arr = [...]int{1, 2, 3, 4}
var slice1 = arr[:] // 結果是 [1 2 3 4],全部元素
var slice2 = arr[1:3] // 結果是 [2 3]
var slice3 = arr[:3] // 結果是 [1 2 3]
var slice4 = arr[1:] // 結果是 [2 3 4]
而在 Rust 中是這麼做的:(結果和上面一樣)
let arr = [1, 2, 3, 4];
let slice1 = &arr[..];
let slice2 = &arr[1..3];
let slice3 = &arr[..3];
let slice4 = &arr[1..];
看到不同了嗎?
-
Rust 中生成切片,需要引用(&);
-
Go 中使用
:
來引用片段;而 Rust 使用..
;
相同的點是,都可以省略起始或終止位置,或都省略。
關於
..
以後還會講到
切片類型的方法(也適用於數組)
在 Rust 中,一切類型都有實現一些 trait,包括上一節的標量類型(用面向對象來講,一切皆對象)。現在先不探討 trait,着重看看 len 方法。具體參考標準庫文檔:https://doc.rust-lang.org/std/primitive.slice.html。
1)len:計算長度
數組或切片有一個 len() 方法可以計算長度。
pub const fn len(&self) -> usize
// 具體使用
let arr = [1, 2, 3];
assert_eq!(arr.len(), 3); // assert_eq 和 println 一樣,是一個宏,用來斷言
而 Go 語言中,使用 len(arr) 的形式,len 是內置函數。
不過,關於 len 還有一些細小的點。看下面的 Go 代碼,你覺得有問題嗎?
var arr = [...]int{1, 2, 3, 4}
var slice = arr[:]
var arr2 [len(arr)]int
var arr3 [len(slice)]int
在 Go 中,要求數組長度要求是編譯期常量。len(arr) 是編譯期常量,而 len(slice) 卻不是,因爲 slice 的長度是可變的。所以,以上代碼 arr2 正確,arr3 編譯錯誤。
那 Rust 中是怎麼樣的呢?
let arr = [1, 2, 3, 4];
let slice = &arr[..];
let arr2 = [0;arr.len()];
let arr3 = [0;slice.len()];
arr2 和 arr3 都編譯錯誤。arr3 錯誤可以理解,爲什麼 arr2 也不行呢?
根據編譯器提示,怎麼修改 arr2 就可以了:
const ARR:[i32; 4] = [1, 2, 3, 4];
let arr2 = [0; ARR.len()];
也就是說必須是數組常量。。。但數組本身不就是不可變的嗎?非得定義成常量,多此一舉?據說,Rust 有可能將數組改成可變的。。。有了切片,爲啥還要把數組搞這麼複雜?!
2)其他方法
-
is_empty:判斷數組或切片是否爲空
-
first:獲取第一個元素
-
last:獲取最後一個元素
-
。。。
first 和 last 有什麼用?爲啥不直接通過下標獲取?
-
last 的存在,使得我們不需要先調用 len 獲取長度來間接獲取最後一個元素。
-
而 first 的存在,使得我們不需要先判斷是否爲空。
不過,因爲存在數組或切片爲空的情況,因此 first 和 last 返回的都是 Opiton 類型。關於該類型後續再講。
04 小結
我們用兩篇講解了 Rust 中的數據類型,同時和 Go 的數據類型進行了對比。但 Rust 中的數據類型不止這些,還有其他類型,我們以後再講,包括通過標準庫定義的數據類型。
再強調一次,本系列教程的目標是讓大家學習儘可能不被勸退,因此有些特別複雜但我認爲可以不用的,就不會介紹。關於 Rust 中的 primitive type 可以在標準庫文檔找到,以及每個類型的方法。https://doc.rust-lang.org/std/index.html#primitives。
我是 polarisxu,北大碩士畢業,曾在 360 等知名互聯網公司工作,10 多年技術研發與架構經驗!2012 年接觸 Go 語言並創建了 Go 語言中文網!著有《Go 語言編程之旅》、開源圖書《Go 語言標準庫》等。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dtYnwOy3FkGjnOV7vshzvg