iT邦幫忙

2024 iThome 鐵人賽

DAY 3
1
自我挑戰組

從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用系列 第 3

[Day 3] Rust 語法速覽:與 Python 的基本語法比較

  • 分享至 

  • xImage
  •  

Rust 與 Python 的基本語法比較

作為一個習慣使用 Python 的開發者,當你開始接觸 Rust 時,可能會遇到一些困難。Rust 與 Python 的語法差異較大,尤其是 Rust 強調的「類別安全」和「不可變性」概念,可能會讓你感到陌生。因此,這篇文章將用簡單的方式幫助你了解幾個 Rust 的核心概念和語法,讓你更順利地上手。

Rust 基本概念速覽

1. 變數的不可變性

Rust 預設變數是不可變的,這和 Python 的靈活變數處理方式不同。要讓變數可以修改,需加上 mut 關鍵字,讓變數變為可變的。

let x = 5;  // 不可變變數
let mut y = 10;  // 可變變數
y = 15;  // 修改可變變數 y 的值
println!("x = {}, y = {}", x, y);

這樣的設計能避免意外修改變數,提升程式的安全性。對 Python 開發者來說,這可能是個新概念。


Rust 資料格式與變數定義總覽

資料格式 語法範例 說明 Python 對應格式
整數 (Integer) let x: i32 = 5; i32 表示 32 位元的整數。Rust 提供多種不同位元大小的整數類別。 int
浮點數 (Float) let y: f64 = 3.14; f64 表示 64 位元的浮點數。Rust 支援 f32f64 兩種浮點數類別。 float
布林值 (Boolean) let is_active: bool = true; bool 類別用於表示 truefalse bool
字元 (Char) let letter: char = 'A'; char 用於儲存單個字元,支援 Unicode,長度為 4 個位元組。 str (單一字元)
字串 (String) let name: String = String::from("Alice"); String 是可變長度的字串,適合需要動態生成或修改的字串。 str
字串切片 (&str) let greeting: &str = "Hello"; &str 是不可變的字串切片,常用於字面字串。 str
陣列 (Array) let numbers: [i32; 3] = [1, 2, 3]; 陣列是一個固定長度的集合,類別和大小在編譯時確定。 list (固定長度)
向量 (Vector) let mut vec: Vec<i32> = Vec::new(); 向量是可變長度的集合,適合儲存大小可變的數據。 list (可變長度)
元組 (Tuple) let tuple: (i32, f64, char) = (42, 6.4, 'Z'); 元組可以包含多種類別的數據,使用索引來存取。 tuple
結構體 (Struct) struct Point { x: i32, y: i32 } 結構體用於定義複合數據類別。 class
列舉 (Enum) enum Direction { Up, Down, Left, Right } 列舉用於定義一組具名常量,表示不同狀態。 無對應
Option 類別 let some_number: Option<i32> = Some(5); Option 用於表示一個值可能存在或不存在。 NoneOptional
Result 類別 let result: Result<i32, String> = Ok(10); Result 用於表示操作結果,成功用 Ok,失敗用 Err 無直接對應
引用 (&) let r: &i32 = &x; 引用用於借用變數,& 表示不可變引用,&mut 表示可變引用。 無直接對應 (Python 是引用型變數)
切片 (Slice) let slice: &[i32] = &numbers[0..2]; 切片用於引用陣列的一部分。 list 切片操作 (如 list[0:2])

變數比較說明:

  • Python 的 intfloat 對應 Rust 的整數和浮點數,但 Python 中 int 沒有固定大小,而 Rust 需明確指定,如 i32f64
  • 字串與字串切片:Rust 中的 String 是可變的動態字串,而 &str 是不可變的字串切片。Python 的 str 更像是 Rust 的 String,但兩者都有不可變性。
  • 陣列與向量:Python 的 list 可靈活變動長度,而 Rust 區分了固定長度的陣列和可變長度的向量。

2. 顯式類別聲明

Rust 要求明確的類別聲明,除非編譯器能夠自動推斷。在 Rust 中,如果未明確宣告變數類別,編譯器會根據上下文自動推斷類別。

let x: i32 = 42;  // 明確定義 i32
let y: &str = "Hello, Rust!";  // 明確定義 &str

當沒有明確宣告類別時,Rust 通常會推斷變數為:

  • 整數:預設為 i32
  • 浮點數:預設為 f64
  • 字串:預設為 &str

如果推斷不明確,如創建空集合,則需顯式定義:

let mut vec = Vec::new();  // 編譯錯誤,無法推斷 Vec 內的類別
let mut vec: Vec<i32> = Vec::new();  // 顯式聲明 Vec<i32>

3. 所有權與借用

Rust 的所有權系統是其核心記憶體管理機制。變數的所有者一旦不再需要它,Rust 會自動釋放記憶體。此外,Rust 提供了「借用」機制,允許不轉移所有權的情況下使用變數。

fn main() {
    let s = String::from("hello");  // s 是所有者
    let len = calculate_length(&s);  // 借用了 s,但沒有取得所有權
    println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: &String) -> usize {  // s 是借用
    s.len()
}

所有權轉移和可變借用是 Rust 記憶體管理中的另一個重要概念:

fn main() {
    let mut s1 = String::from("world");
    modify_string(&mut s1);  // 可變借用 s1 進行修改
    println!("{}", s1);  // 修改後的 s1
}

fn modify_string(s: &mut String) {
    s.push_str(", Rust!");  // 修改借用來的變數
}

4. 模式匹配

Rust 的 match 語句功能強大,類似於 Python 的 if-elif,但更具彈性和表達力。

let number = 2;
match number {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Something else"),  // `_` 匹配所有其他情況
}

match 語句可幫助簡化代碼,避免重複條件判斷,提升可讀性。


5. fn main() 函數

在 Rust 中,fn main() 是每個程式的入口點,類似於 C、C++ 或 Java 中的 main 函數。程式執行時,會先從 main 函數開始執行。因此,所有的程式碼要執行時,必須包裹在 main 函數內。這與 Python 不同,Python 程式碼從上至下依次執行,不需要專門定義一個主函數。

fn main() {
    println!("Hello, Rust!");
}

Rust 編譯後的程式會自動從 main 函數開始執行。與此不同,Python 的直譯程式會逐行執行程式碼,並不強制要求主程式入口點。這意味著,在 Python 中,不需要使用專門的 main 函數來啟動程式。

print("Hello, Python!")

然而,如果你希望在 Python 中模擬類似的結構,可以使用 if __name__ == "__main__": 作為入口點判斷,但這在 Python 中不是強制性的:

def main():
    print("Hello, Python!")

if __name__ == "__main__":
    main()

小結:

  • Rust:程式必須有 fn main() 作為入口點,並從此處開始執行。
  • Python:程式從上到下執行,無需明確的主函數,但可以使用 if __name__ == "__main__": 來模擬類似結構。

變數宣告:自由與安全的平衡

Python:靈活但無約束

在 Python

中,變數不需要指定類別,並且可以自由更改。

x = 42  # 整數
x = "Now I'm a string!"  # 可以動態改變類別
print(x)

Rust:嚴謹且安全

Rust 的變數類別需在定義時確定,預設不可變,這種設計提升了程式的安全性和穩定性,特別是在多線程環境下。

let mut x = 42;  // 用 `mut` 來定義可變變數
x = 100;
println!("{}", x);

Rust 的 Vec 和迭代器提供了高效的數據處理方式:

fn main() {
    let numbers: Vec<i32> = (1..6).collect();  // 創建 Vec
    let sum: i32 = numbers.iter().sum();  // 使用迭代器計算總和
    println!("Sum of numbers: {}", sum);
}

函數定義:簡潔與精確

Python:靈活多變

Python 中的函數定義不需要指定參數類別或返回值類別,靈活方便。

def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))

Rust:精確的類別聲明

Rust 要求函數參數和返回值類別必須明確,這雖然使代碼更嚴謹,但能有效避免錯誤。

fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

println!("{}", greet("Alice"));

若需要實現 Python 中的預設參數效果,可使用 Option

fn greet(name: Option<&str>) -> String {
    match name {
        Some(n) => format!("Hello, {}!", n),
        None => "Hello, World!".to_string(),
    }
}

println!("{}", greet(Some("Alice")));  // Hello, Alice!
println!("{}", greet(None));  // Hello, World!

性能測試簡單比較

Python 和 Rust 在處理計算密集任務時的性能存在顯著差異。這裡以計算前 1000 萬個數字的總和為例:

Python 範例:

import time

start_time = time.time()

def calculate_sum(n):
    return sum(range(n))

result = calculate_sum(10000000)
end_time = time.time()

print(f"Result: {result}, Time taken: {end_time - start_time} seconds")

Rust 範例:

use std::time::Instant;

fn calculate_sum(n: u64) -> u64 {
    (0..n).sum()
}

fn main() {
    let start = Instant::now();

    let result = calculate_sum(10_000_000);
    let duration = start.elapsed();

    println!("Result: {}, Time taken: {:?}", result, duration);
}

性能結果:

  • Python:實測花費0.26 秒完成計算。
  • Rust:實測花費0.062 秒完成計算。

總結

在這篇文章中,我們比較了 Python 和 Rust 的語法差異。Python 靈活性強,適合快速開發;Rust 重視性能與安全,適合大型、高效能應用程式。希望這篇文章能幫助你更輕鬆地理解 Rust,並將其運用到日常開發中!


上一篇
[Day 2] 安裝與設定 Rust 開發環境:初始步驟
下一篇
[Day 4] 建構任務管理工具:Rust 變數與資料類別的實戰應用
系列文
從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言