Rust 使用 Cargo 作為套件管理器和建構工具,能自動處理專案建構、依賴管理等繁瑣任務。開發者可以使用 Cargo 建立新專案、新增依賴、編譯程式碼和執行測試,簡化開發流程並確保程式碼品質。Cargo 的組態檔案 Cargo.toml 使用 TOML 格式,清晰易懂。除了基本建構功能外,Cargo 也提供清理、檢查等實用命令,方便開發者管理專案。瞭解 Cargo 的使用方法對於 Rust 開發至關重要,能有效提升開發效率。
Rust 的資料型別系統嚴謹且豐富,區分為純量型別和複合型別。純量型別包含整數、浮點數、布林值和字元等基本資料型別,各有不同的大小和表示範圍。複合型別則由多個值組成,例如 Tuple 可以包含不同型別的元素,而 Array 則包含相同型別的元素。Rust 的變數預設為不可變,可以透過 mut 關鍵字使其可變。理解 Rust 的資料型別系統對於撰寫安全且高效的程式碼至關重要,能避免許多常見的程式錯誤。
使用Cargo管理Rust專案
Cargo是Rust的套件管理器和建構系統,負責處理程式碼及其依賴項的建構。隨著程式變得越來越複雜,Cargo的作用也變得越來越重要。在未來的課程中,我們將使用Cargo來管理我們的專案。
使用Cargo建立Hello World專案
在上一節中,我們手動建立了Hello World程式的專案目錄和原始檔。現在,我們可以使用Cargo來避免這些手動步驟。以下命令可以用來建立相同的專案:
cargo new hello_world_cargo
cargo new命令會建立一個新的專案目錄,並在該目錄中建立必要的檔案。你可以進入專案目錄並使用tree命令來檢視目錄和檔案:
tree .
如果你遇到錯誤,可能是因為你的系統上沒有安裝tree程式。你可以安裝tree或使用ls命令來列出檔案。在Windows系統上,tree程式預設已安裝,但你需要在tree後面加上/f引數來列出目錄內的檔案:
tree . /f
你可以看到Cargo建立了兩個檔案:Cargo.toml和src/main.rs,以及一個目錄:src。它還為你初始化了一個Git儲存函式庫。Git是預設的版本控制系統,可以透過--vcs標誌進行更改。
瞭解Cargo.toml檔案
Cargo產生的Cargo.toml組態檔案應該如下所示:
[package]
name = "hello_world_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
這個檔案的格式是Tom’s Obvious, Minimal Language(TOML),它是Cargo的組態格式。檔案的開頭是一個名為[package]的部分,後面跟著Cargo編譯程式碼所需的組態細節:名稱、版本、作者和Rust版本。如果我們的程式依賴其他函式庫,則應將其新增到[dependencies]部分下,Cargo將為我們下載和建構這些函式庫。
依賴項型別
依賴項可以是以下幾種型別:
[dependencies]:這些是套件函式庫或二進位制依賴項。例如,如果你想使用base64,可以在這裡新增一行簡單的程式碼,如base64 = "*"。[dev-dependencies]:這些是開發依賴項,例如測試和基準測試。它們不會傳播到依賴此套件的其他套件。[build-dependencies]:透過這個部分,你可以指定在建構指令碼中使用的其他根據Cargo的crate的依賴項。這些依賴項將用於build.rs檔案中。[target]:這個部分用於對各種目標架構進行跨平台編譯。
使用Cargo建構和執行程式
要建構和執行我們的程式,我們可以使用cargo run命令:
cargo run
Cargo會呼叫Rust編譯器rustc並執行產生的可執行檔。可執行檔位於套件頂層目錄中的target資料夾中。我們也可以使用cargo build單獨建構程式,然後執行可執行檔,或者使用cargo run。
建構設定檔
Cargo提供了四種型別的建構設定檔:
dev:這是cargo build命令預設使用的設定檔。release:這是使用cargo build --release命令時使用的設定檔。它將在target/release中建立可執行檔,而不是在target/debug中。
其他Cargo命令
cargo clean:清理產生的檔案。cargo check:快速檢查程式碼是否可以編譯,但不產生可執行檔。
內容解密:
- cargo new:用於建立新的Rust專案。
- Cargo.toml:組態檔案,用於定義專案的中繼資料和依賴關係。
- src/main.rs:主要的Rust原始碼檔案,包含程式的入口點。
- cargo run 和 cargo build:用於建構和執行Rust程式的命令。
- cargo check:用於檢查程式碼是否可以編譯的命令。
圖表翻譯:
此圖示顯示了使用Cargo建立和管理Rust專案的基本流程,包括建立專案、組態依賴項、建構和執行程式等步驟。
Rust 程式語言基礎
在前一章中,我們撰寫了一個簡單的 Rust 程式,並在控制檯上印出訊息。本章將涵蓋 Rust 語言的一些基本概念,其中大多數概念在其他程式語言中也很常見。我們將介紹變數、資料型別、函式、迴圈和控制流程陳述式等概念。
本章結構
本章將涵蓋以下主題:
- 變數
- 資料型別
- 新增註解
- 函式
- 控制流程陳述式和迴圈
目標
完成本章後,您將能夠理解 Rust 程式語言的一些基本程式設計概念。您將學習到變數、不同型別的資料和變數的可變性。此外,您還將學習到函式和控制流程陳述式,這將有助於您組織程式碼。
變數
變數是一個命名的儲存位置,它將符號名稱對映到儲存在電腦記憶體中的值。幾乎所有的程式語言都使用變數來儲存和操作資料。編譯器會用實際的資料位置替換變數名稱。
宣告變數
在 Rust 中,使用 let 關鍵字宣告變數,後面跟著變數名稱、等於符號 (=) 和值。讓我們宣告一個變數,指定給它並印出它的值,如下所示的程式碼片段:
fn main() {
let x = 2;
println!("x = {}", x);
}
上述程式碼的輸出應該如下:
x = 2
在上述程式碼片段中,let x = 2; 陳述式宣告了一個名為 x 的變數,並指定為 2。let 關鍵字建立了一個新的變數 x,可以在宣告的作用域記憶體取。等於符號將右邊的值賦給左邊的變數 x。大括號 {} 包含在引號 "{}" 內,作為一個佔位符,被 println! 巨集替換為傳遞給它的第二個引數的值。這裡,變數 x 的值是 2,因此 {} 被替換為 2。
可變性
讓我們嘗試修改前面宣告的變數 x 的值,並印出 x 的更新值,如下所示的程式碼片段:
fn main() {
let x = 2;
println!("x = {}", x);
x = 3;
println!("x = {}", x);
}
如果您嘗試執行這段程式碼,您應該會收到錯誤,如下圖 2.1 所示:
錯誤原因是因為嘗試對不可變的變數 x 指定兩次。在 Rust 中,預設情況下所有變數都是不可變的,除非明確宣告為可變的。編譯器進一步建議,可以使用 mut 關鍵字在變數名稱前宣告可變性,如下所示的程式碼片段:
fn main() {
let mut x = 2;
println!("x = {}", x);
x = 3;
println!("x = {}", x);
}
現在,程式碼應該可以正常工作,並印出變數 x 的初始值和更新值,如下圖 2.2 所示:
資料型別
電腦表示資料的方式與人類表示資料的方式非常不同。電腦使用一連串的 0 和 1(稱為位元)來表示資料。一個位元可以表示兩個值,即 0 和 1。一連串的 8 個位元稱為一個位元組。讓我們透過一個例子來理解它。假設有一連串的位元 00101010。在十進製表示中,這一連串位元代表數字 42。同樣的位元序列也可以用來表示字元 '*'。因此,我們需要將一些額外的資訊與這個位元序列相關聯,無論我們是要表示數字 42 還是字元 '*'。這種額外的資訊,即表示我們要表示的資料型別,被稱為資料型別。Rust 是一種靜態型別語言,變數的資料型別必須在編譯時被 Rust 編譯器所知。大多數情況下,變數的資料型別可以從其值推斷出來,但並非總是如此。在這種情況下,必須在宣告變數時新增資料型別註解。可以透過在變數名稱後面新增冒號 (:) 和資料型別來註解變數,如下所示:
let x: i32 = 2;
這裡,變數 x 的型別是 i32,即使用 32 位元記憶體的有符號整數。
資料型別註解的重要性
Rust 需要明確的資料型別註解,以確保編譯器能夠正確地理解程式碼。這有助於避免因型別不符而導致的錯誤。
重點回顧
- Rust 使用
let關鍵字宣告變數。 - Rust 中的變數預設是不可變的,可以使用
mut關鍵字宣告為可變的。 - 資料型別是 Rust 中的重要概念,用於定義變數可以儲存的值的型別。
- Rust 需要明確的資料型別註解,以確保編譯器能夠正確地理解程式碼。
練習題
- Rust 中如何宣告一個變數?
- Rust 中的變數預設是可變的還是不可變的?
- 如何在 Rust 中宣告一個可變的變數?
- 資料型別在 Rust 中的作用是什麼?
- 如何在 Rust 中為變數新增資料型別註解?
資料型別在Rust中的重要性
在Rust程式語言中,資料型別扮演著至關重要的角色。編譯器有時會因為變數缺乏明確的資料型別註解而產生混淆,進而導致編譯錯誤。以下是一個簡單的範例:
fn main(){
let x = "2".parse().unwrap();
println!("x = {}", x);
}
執行上述程式碼將會產生錯誤,如下圖所示:
此圖示:錯誤由於缺少型別註解
錯誤訊息指出需要為變數x進行型別註解。因此,我們可以為變數x註解型別u8,如下所示:
fn main(){
let x: u8 = "2".parse().unwrap();
println!("x = {}", x);
}
輸出結果:x = 2
Rust中的資料型別分類別
Rust中的資料型別主要分為兩大類別:純量(Scalar)和複合(Compound)。
純量資料型別
純量資料型別包括整數、浮點數、字元和布林值等。
整數
整數資料型別用於表示不帶小數的數值。Rust中的整數型別取決於是否帶有符號以及所佔用的位元數,如下表所示:
| 位元組數 | 無符號 | 有符號 |
|---|---|---|
| 8 | u8 | i8 |
| 16 | u16 | i16 |
| 32 | u32 | i32 |
| 64 | u64 | i64 |
| 128 | u128 | i128 |
| 架構特定(32/64) | usize | isize |
此表格:Rust中的不同整數型別
有符號整數使用1個位元來表示符號,其餘位元用於表示數值。因此,它們可以表示正負數值。無符號整數則使用所有位元來表示數值,因此只能表示正數值。
預設的整數型別為i32,即如果未明確指定整數變數的型別,Rust編譯器將預設使用i32。
浮點數
浮點數資料型別用於儲存帶有小數點的數值。Rust提供兩種浮點數型別:f32和f64,分別佔用32位元和64位元。預設的浮點數型別為f64。
fn main() {
let a = 1.5; // a 是 f64
let b: f32 = 2.5; // b 是 f32
}
布林值
布林值資料型別用於儲存二元值:true和false。布林變數的大小為1位元組。
fn main() {
let f = false;
let t: bool = true;
}
字元
字元資料型別用於表示單一字元,如字母或數字。Rust中的字元是Unicode純量值,佔用4位元組。
fn main() {
let first_char = 'B';
let second_char = '2';
let third_char = '\u{2A}'; // Unicode 值對應到 '*'
println!("first_char = {}, second_char = {}, third_char = {}", first_char, second_char, third_char);
}
輸出結果:first_char = B, second_char = 2, third_char = *
複合資料型別
複合資料型別可以儲存多個值,主要包括元組(Tuples)和陣列(Arrays)。
元組
元組是一種可以儲存多個相關值的複合資料型別。與陣列不同,元組可以儲存不同型別的值。元組的元素以逗號分隔,置於一對括號內。
// 元組範例
let tuple: (&str, i32, f64) = ("Rust", 2021, 3.14);
println!("tuple = {:?}", tuple);
圖表翻譯: 上述程式碼展示瞭如何宣告和使用元組,其中包含不同型別的元素。
陣列
陣列是一種儲存相同型別的多個值的複合資料型別。陣列的大小是固定的,一旦宣告就不能改變。
// 陣列範例
let array: [i32; 5] = [1, 2, 3, 4, 5];
println!("array = {:?}", array);
圖表翻譯: 上述程式碼展示瞭如何宣告和使用陣列,其中包含相同型別的元素。
複合資料型別:Tuple 與 Array
在 Rust 程式語言中,複合資料型別是用於儲存多個值的資料結構。本章節將探討兩種重要的複合資料型別:Tuple(元組)與 Array(陣列)。
Tuple(元組)
Tuple 是一種可以儲存多個不同型別值的資料結構。Tuple 中的元素可以是不同的資料型別,例如整數、浮點數、字元等。
Tuple 的基本使用
要建立一個 Tuple,可以使用小括號 () 將多個值包起來,並以逗號分隔。例如:
let t = (2.5, 'b', 2);
要存取 Tuple 中的元素,可以使用點號 . 後面跟著元素的索引(index)。例如:
let second_elem = t.1;
println!("Second element = {}", second_elem);
輸出結果為:Second element = b
Tuple 的型別註解
Tuple 中的元素會根據其值自動推斷出其資料型別。如果需要明確指定 Tuple 中元素的資料型別,可以使用型別註解。例如:
let t: (f32, char, u8) = (2.5, 'b', 2);
Tuple 的不可變性
預設情況下,Tuple 是不可變的,也就是說,一旦指定後就不能更改其值。如果需要更改 Tuple 中的元素,需要將 Tuple 宣告為可變的。例如:
let mut t_mut = (2.5, 'b', 2);
t_mut.1 = 'z';
println!("Second element after mutation = {}", t_mut.1);
輸出結果為:Second element after mutation = z
#### 內容解密:
上述程式碼展示瞭如何建立一個可變的 Tuple 並更改其元素的值。其中,let mut t_mut = (2.5, 'b', 2); 建立了一個可變的 Tuple,t_mut.1 = 'z'; 將第二個元素更改為 'z'。
Array(陣列)
Array 是一種儲存多個相同型別值的資料結構。Array 中的元素儲存在連續的記憶體空間中,其長度是固定的。
Array 的基本使用
要建立一個 Array,可以使用中括號 [] 將多個值包起來,並以逗號分隔。例如:
let ar = ['a', 'b', 'c'];
要存取 Array 中的元素,可以使用中括號 [] 後面跟著元素的索引(index)。例如:
println!("First element = {}", ar[0]);
輸出結果為:First element = a
Array 的不可變性
預設情況下,Array 是不可變的,也就是說,一旦指定後就不能更改其值。如果需要更改 Array 中的元素,需要將 Array 宣告為可變的。例如:
let mut ar = [1, 2, 3];
ar[2] = 30;
println!("Third Element = {}", ar[2]);
輸出結果為:Third Element = 30
#### 內容解密:
上述程式碼展示瞭如何建立一個可變的 Array 並更改其元素的值。其中,let mut ar = [1, 2, 3]; 建立了一個可變的 Array,ar[2] = 30; 將第三個元素更改為 30。
Array 的初始化
如果需要在宣告 Array 時不指定,可以使用型別註解指定 Array 的型別和長度。例如:
let ar: [u8; 3];
但是,在使用 Array 之前,需要對其進行初始化,否則會出現編譯錯誤。
#### 內容解密:
上述程式碼展示瞭如何宣告一個未初始化的 Array。其中,let ar: [u8; 3]; 宣告了一個長度為 3 的 u8 型別 Array,但未指定。在使用前,需要對其進行初始化。
圖表說明
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Rust Cargo 專案管理與資料型別
package "Rust 記憶體管理" {
package "所有權系統" {
component [Owner] as owner
component [Borrower &T] as borrow
component [Mutable &mut T] as mutborrow
}
package "生命週期" {
component [Lifetime 'a] as lifetime
component [Static 'static] as static_lt
}
package "智慧指標" {
component [Box<T>] as box
component [Rc<T>] as rc
component [Arc<T>] as arc
component [RefCell<T>] as refcell
}
}
package "記憶體區域" {
component [Stack] as stack
component [Heap] as heap
}
owner --> borrow : 不可變借用
owner --> mutborrow : 可變借用
owner --> lifetime : 生命週期標註
box --> heap : 堆積分配
rc --> heap : 引用計數
arc --> heap : 原子引用計數
stack --> owner : 棧上分配
note right of owner
每個值只有一個所有者
所有者離開作用域時值被釋放
end note
@enduml圖表翻譯: 此圖示展示了 Tuple 和 Array 的不可變性和可變性。Tuple 和 Array 都可以是不可變或可變的,取決於其宣告方式。