iT邦幫忙

2025 iThome 鐵人賽

DAY 30
0
Rust

Rust 逼我成為更好的工程師:從 Borrow Checker 看軟體設計系列 第 30

(Day30) 遷移與總結:從 Laravel、Vue、Python、Go 到 Rust 的對照

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250917/20124462KA2M7PfuNm.png

Rust 逼我成為更好的工程師:遷移與總結:從 Laravel、Vue、Python、Go 到 Rust 的對照

經過 30 天的探索,我們深入理解了 Rust 的主要概念:所有權、借用、生命週期。

今天我們要回答最實務的問題:如何將 Rust 的思維應用到日常工作中?何時該用 Rust,何時不該?

從其他語言到 Rust:思維模型的轉變

PHP/Laravel:從隱式到顯式

// PHP:隱式共享,執行期錯誤
class UserService {
    private $db;
    
    public function getUser($id) {
        $user = $this->db->query("SELECT * FROM users WHERE id = ?", [$id]);
        if (!$user) {
            return null;  // 可能被忽略
        }
        return $user;
    }
}

// 使用時容易出錯
$user = $service->getUser(123);
echo $user->name;  // 如果 $user 是 null?執行期錯誤
// Rust:顯式錯誤處理,編譯期保證
struct UserService {
    db: Database,
}

impl UserService {
    fn get_user(&self, id: u64) -> Result<User, Error> {
        self.db.query_one("SELECT * FROM users WHERE id = $1", &[&id])
    }
}

// 使用時必須處理錯誤
fn use_service(service: &UserService) {
    match service.get_user(123) {
        Ok(user) => println!("{}", user.name),
        Err(e) => eprintln!("錯誤: {}", e),
    }
}

關鍵轉變

  • 錯誤處理:從可選到強制
  • 型別安全:從執行期到編譯期
  • 記憶體管理:從 GC 到所有權

JavaScript/Vue:從可變到不可變思維

// Vue:可變狀態
export default {
  data() {
    return {
      users: []
    }
  },
  methods: {
    addUser(user) {
      this.users.push(user);  // 直接修改
    },
    updateUser(id, newData) {
      const user = this.users.find(u => u.id === id);
      if (user) {
        Object.assign(user, newData);  // 直接修改
      }
    }
  }
}
// Rust:不可變預設,明確可變
struct AppState {
    users: Vec<User>,
}

impl AppState {
    fn add_user(&mut self, user: User) {
        self.users.push(user);  // 需要 &mut self
    }
    
    fn update_user(&mut self, id: u64, new_data: UserData) -> Result<(), Error> {
        let user = self.users.iter_mut()
            .find(|u| u.id == id)
            .ok_or(Error::NotFound)?;
        user.update(new_data);
        Ok(())
    }
    
    // 不可變方法不需要 mut
    fn get_user(&self, id: u64) -> Option<&User> {
        self.users.iter().find(|u| u.id == id)
    }
}

關鍵轉變

  • 可變性:從預設可變到預設不可變
  • 借用:從隱式共享到顯式借用
  • 生命週期:從 GC 到編譯期檢查

Python:從動態到靜態

# Python:動態型別,執行期檢查
def process_data(data):
    if isinstance(data, list):
        return sum(data)
    elif isinstance(data, dict):
        return sum(data.values())
    else:
        raise TypeError("不支援的型別")

# 使用時可能出錯
result = process_data("hello")  # 執行期錯誤
// Rust:靜態型別,編譯期檢查
enum Data {
    List(Vec<i32>),
    Dict(HashMap<String, i32>),
}

fn process_data(data: Data) -> i32 {
    match data {
        Data::List(list) => list.iter().sum(),
        Data::Dict(dict) => dict.values().sum(),
    }
}

// 使用時型別安全
fn use_process() {
    let data = Data::List(vec![1, 2, 3]);
    let result = process_data(data);
    
    // 無法傳入錯誤型別
    // let result = process_data("hello");  // ❌ 編譯錯誤
}

關鍵轉變

  • 型別系統:從動態到靜態
  • 錯誤檢查:從執行期到編譯期
  • 效能:從解釋到編譯

Go:從 GC 到所有權

// Go:GC 管理記憶體
type Cache struct {
    data map[string]*Data
    mu   sync.RWMutex
}

func (c *Cache) Get(key string) *Data {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.data[key]  // 回傳指標,GC 處理生命週期
}

func (c *Cache) Set(key string, value *Data) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value  // GC 處理舊值
}
// Rust:所有權管理記憶體
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

struct Cache {
    data: Arc<RwLock<HashMap<String, Arc<Data>>>>,
}

impl Cache {
    fn get(&self, key: &str) -> Option<Arc<Data>> {
        let data = self.data.read().unwrap();
        data.get(key).cloned()  // 回傳 Arc,明確共享所有權
    }
    
    fn set(&self, key: String, value: Arc<Data>) {
        let mut data = self.data.write().unwrap();
        data.insert(key, value);  // 舊值自動 Drop
    }
}

關鍵轉變

  • 記憶體管理:從 GC 到所有權
  • 並發:從 goroutine 到 Send/Sync
  • 錯誤處理:從 error 回傳到 Result

概念對照表

記憶體管理

概念 PHP/JS Python Go Rust
記憶體管理 GC GC GC 所有權
共享 隱式複製/引用 引用 指標 &T / Arc<T>
可變 預設可變 預設可變 預設可變 預設不可變
生命週期 GC 決定 GC 決定 GC 決定 編譯期決定

錯誤處理

概念 PHP/JS Python Go Rust
錯誤類型 Exception Exception error 值 Result<T, E>
處理方式 try/catch try/except if err != nil match / ?
強制處理
型別安全

並發模型

概念 PHP JS Python Go Rust
並發模型 多進程 Event Loop GIL goroutine 執行緒/async
共享狀態 單執行緒 channel/鎖 Arc<Mutex>
安全保證 單執行緒 執行期 執行期 編譯期

實戰遷移案例

案例 1:API 伺服器(從 Laravel 到 Rust)

// Laravel
class UserController extends Controller {
    public function show($id) {
        $user = User::find($id);
        if (!$user) {
            return response()->json(['error' => 'Not found'], 404);
        }
        return response()->json($user);
    }
}
// Rust (使用 axum)
use axum::{
    extract::Path,
    http::StatusCode,
    Json,
    response::IntoResponse,
};

async fn get_user(
    Path(id): Path<u64>,
    State(db): State<Database>,
) -> Result<Json<User>, AppError> {
    let user = db.get_user(id).await?;
    Ok(Json(user))
}

// 錯誤處理
impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        match self {
            AppError::NotFound => {
                (StatusCode::NOT_FOUND, "User not found").into_response()
            }
            AppError::Database(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
            }
        }
    }
}

遷移要點

  • 錯誤處理從可選到強制
  • 型別安全從執行期到編譯期
  • 非同步從 PHP-FPM 到 async/await

案例 2:資料處理(從 Python 到 Rust)

# Python
def process_logs(filename):
    errors = []
    with open(filename) as f:
        for line in f:
            if 'ERROR' in line:
                errors.append(parse_error(line))
    return errors
// Rust
use std::fs::File;
use std::io::{BufRead, BufReader};

fn process_logs(filename: &str) -> Result<Vec<Error>, std::io::Error> {
    let file = File::open(filename)?;
    let reader = BufReader::new(file);
    
    let errors: Result<Vec<_>, _> = reader
        .lines()
        .filter_map(|line| {
            let line = line.ok()?;
            if line.contains("ERROR") {
                Some(parse_error(&line))
            } else {
                None
            }
        })
        .collect();
    
    errors
}

遷移要點

  • 錯誤處理更明確
  • 記憶體使用更可控
  • 效能提升顯著(10-100x)

案例 3:併發處理(從 Go 到 Rust)

// Go
func processItems(items []Item) []Result {
    results := make(chan Result, len(items))
    var wg sync.WaitGroup
    
    for _, item := range items {
        wg.Add(1)
        go func(item Item) {
            defer wg.Done()
            results <- process(item)
        }(item)
    }
    
    wg.Wait()
    close(results)
    
    var output []Result
    for r := range results {
        output = append(output, r)
    }
    return output
}
// Rust
use tokio::task;

async fn process_items(items: Vec<Item>) -> Vec<Result> {
    let handles: Vec<_> = items
        .into_iter()
        .map(|item| {
            task::spawn(async move {
                process(item).await
            })
        })
        .collect();
    
    let mut results = Vec::new();
    for handle in handles {
        if let Ok(result) = handle.await {
            results.push(result);
        }
    }
    
    results
}

遷移要點

  • 型別安全的並發
  • 編譯期檢查資料競爭
  • 零成本抽象

何時該用 Rust?

✅ 適合使用 Rust 的場景

  1. 效能關鍵路徑
// 高頻交易、遊戲引擎、影像處理
fn hot_path(data: &[f64]) -> f64 {
    data.iter().sum()  // 零成本抽象
}
  1. 系統程式設計
// 作業系統、驅動程式、嵌入式系統
unsafe fn direct_hardware_access() {
    // 完全控制記憶體
}
  1. 可靠性要求高
// 金融系統、醫療系統、航空系統
fn critical_operation() -> Result<(), Error> {
    // 編譯期保證
}
  1. CLI 工具
// ripgrep, fd, bat, exa
fn main() {
    // 快速啟動、低記憶體使用
}
  1. WebAssembly
#[wasm_bindgen]
pub fn compute(data: &[u8]) -> Vec<u8> {
    // 在瀏覽器中執行
}

❌ 不適合使用 Rust 的場景

  1. 快速原型
# Python 更適合
def prototype():
    # 快速驗證想法
    pass
  1. 資料科學
# Python 生態系更成熟
import pandas as pd
df = pd.read_csv('data.csv')
  1. 簡單的 CRUD API
// Laravel 更快速
Route::get('/users', [UserController::class, 'index']);
  1. 前端開發
// React/Vue 更適合
export default {
  // 快速開發 UI
}

決策流程圖

是否需要極致效能?
├─ 是 → 考慮 Rust
└─ 否 → 是否需要記憶體安全保證?
    ├─ 是 → 考慮 Rust
    └─ 否 → 是否是系統程式設計?
        ├─ 是 → 考慮 Rust
        └─ 否 → 是否需要快速開發?
            ├─ 是 → 使用熟悉的語言
            └─ 否 → 評估團隊技能和專案需求

漸進式採用策略

策略 1:從邊界開始

現有系統 (Python/Go)
    ↓
    ↓ 呼叫
    ↓
Rust 模組 (效能關鍵部分)
# Python 呼叫 Rust
import my_rust_module

def process_data(data):
    # 關鍵路徑用 Rust
    result = my_rust_module.fast_process(data)
    # 其他邏輯用 Python
    return transform(result)

策略 2:新功能用 Rust

現有系統 (PHP/Laravel)
    ↓
    ↓ HTTP API
    ↓
新服務 (Rust)

策略 3:重寫瓶頸

1. 剖析找出瓶頸
2. 用 Rust 重寫瓶頸部分
3. 透過 FFI 整合
4. 驗證效能提升
5. 逐步擴展

30 天的總結:Rust 教會我們什麼?

1. 明確性勝過隱式

// Rust 強迫我們明確表達意圖
fn process(data: &str) -> Result<Output, Error> {
    // 錯誤處理:明確
    // 所有權:明確
    // 生命週期:明確
}

應用到其他語言

  • 明確處理錯誤,不要忽略
  • 明確文件化所有權語義
  • 明確標註可變性

2. 編譯期勝過執行期

// 能在編譯期檢查的,不要留到執行期
fn safe_divide(a: i32, b: NonZeroI32) -> i32 {
    a / b  // 編譯期保證不會除以零
}

應用到其他語言

  • 使用型別系統表達約束
  • 使用靜態分析工具
  • 寫更多的單元測試

3. 不變性勝過可變性

// 預設不可變,需要時才可變
let data = vec![1, 2, 3];  // 不可變
let mut data = vec![1, 2, 3];  // 明確可變

應用到其他語言

  • 優先使用 const/final
  • 避免修改參數
  • 使用不可變資料結構

4. 所有權思維

// 清楚誰擁有資料
fn take_ownership(data: String) { }  // 拿走
fn borrow_data(data: &str) { }       // 借用

應用到其他語言

  • 文件化誰負責釋放資源
  • 使用 RAII 模式
  • 避免共享可變狀態

最後的建議

給 PHP/Laravel 開發者

  1. 從 CLI 工具開始學 Rust
  2. 用 Rust 重寫效能瓶頸
  3. 學習明確的錯誤處理
  4. 理解所有權 vs GC

給 JavaScript/Vue 開發者

  1. 從 WebAssembly 開始
  2. 學習靜態型別思維
  3. 理解不可變性的價值
  4. 用 Rust 處理計算密集任務

給 Python 開發者

  1. 從資料處理工具開始
  2. 用 PyO3 整合 Rust
  3. 學習型別系統的力量
  4. 理解效能與安全的權衡

給 Go 開發者

  1. 從併發模式開始
  2. 理解所有權 vs GC
  3. 學習零成本抽象
  4. 比較 channel vs Arc<Mutex>

結語:成為更好的工程師

Rust 不只是一門語言,更是一種思維方式:

  • 明確勝過隱式
  • 編譯期勝過執行期
  • 不變性勝過可變性
  • 所有權勝過垃圾回收

這些原則不只適用於 Rust,也能讓我們在任何語言中寫出更好的程式碼。

Rust 逼我成為更好的工程師,不是因為它的語法,而是因為它改變了我思考程式的方式。

相關連結與參考資源


上一篇
(Day29) Rust 測試策略:單元、整合、性質測試
系列文
Rust 逼我成為更好的工程師:從 Borrow Checker 看軟體設計30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言