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.tomlsrc/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:快速檢查程式碼是否可以編譯,但不產生可執行檔。

內容解密:

  1. cargo new:用於建立新的Rust專案。
  2. Cargo.toml:組態檔案,用於定義專案的中繼資料和依賴關係。
  3. src/main.rs:主要的Rust原始碼檔案,包含程式的入口點。
  4. cargo runcargo build:用於建構和執行Rust程式的命令。
  5. 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 需要明確的資料型別註解,以確保編譯器能夠正確地理解程式碼。

練習題

  1. Rust 中如何宣告一個變數?
  2. Rust 中的變數預設是可變的還是不可變的?
  3. 如何在 Rust 中宣告一個可變的變數?
  4. 資料型別在 Rust 中的作用是什麼?
  5. 如何在 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中的整數型別取決於是否帶有符號以及所佔用的位元數,如下表所示:

位元組數無符號有符號
8u8i8
16u16i16
32u32i32
64u64i64
128u128i128
架構特定(32/64)usizeisize

此表格:Rust中的不同整數型別

有符號整數使用1個位元來表示符號,其餘位元用於表示數值。因此,它們可以表示正負數值。無符號整數則使用所有位元來表示數值,因此只能表示正數值。

預設的整數型別為i32,即如果未明確指定整數變數的型別,Rust編譯器將預設使用i32

浮點數

浮點數資料型別用於儲存帶有小數點的數值。Rust提供兩種浮點數型別:f32f64,分別佔用32位元和64位元。預設的浮點數型別為f64

fn main() {
    let a = 1.5;  // a 是 f64
    let b: f32 = 2.5;  // b 是 f32
}

布林值

布林值資料型別用於儲存二元值:truefalse。布林變數的大小為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 都可以是不可變或可變的,取決於其宣告方式。