博客 / 詳情

返回

Rust簡要彙總(持續更新)

Rust工具: https://www.rust-lang.org/tools/install

1 cargo

cargo new my_test

開始於單元包的根節點:在編譯一個單元包時,編譯器會從單元包的根節點文件開始編譯(通常是庫單元包中的src/lib.rs,或二進制單元包中的src/main.rs)​。

2 thread

在 Rust 中,handle.join().unwrap() 是用於等待線程完成並獲取其返回值的常見操作。
join() 方法返回一個 Result<T, Box<dyn Error>>,其中 T 是被等待線程的返回值類型。使用 unwrap() 是一種簡單的錯誤處理方式,它會:

  • 如果結果是 Ok(t),則返回內部的值 t
  • 如果結果是 Err(e),則會觸發 panic 並顯示錯誤信息
fn main() {
    // 創建一個線程並獲取其句柄
    let handle = thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        "線程執行完成" // 線程的返回值
    });

    // 等待線程完成並獲取返回值
    let result = handle.join().unwrap();
    println!("{}", result); // 輸出: 線程執行完成
}

在 Rust 中,let _ = handle.join(); 是一種處理線程 JoinHandle 的方式,它的作用是:

  1. 調用 join() 方法阻塞當前線程,等待被 spawn 的線程執行完成
  2. 使用 let _ = 忽略 join() 返回的 Result 值
    與 handle.join().unwrap() 不同,這種寫法會靜默忽略任何可能的錯誤,包括線程恐慌。

3 安裝

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

或者

wget --https-only --secure-protocol=TLSv1_2 -qO- https://sh.rustup.rs | sh

刷新環境變量
安裝完成後,需要讓終端識別新安裝的 rustup 命令,執行:

source $HOME/.cargo/env

3.1 問題

  1. 安裝rustup時報錯:
[22:35:07] root@ceph-221:/home/code/eza# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer
warn: It looks like you have an existing installation of Rust at:
warn: /usr/bin
warn: It is recommended that rustup be the primary Rust installation.
warn: Otherwise you may have confusion unless you are careful with your PATH.
warn: If you are sure that you want both rustup and your already installed Rust
warn: then please reply `y' or `yes' or set RUSTUP_INIT_SKIP_PATH_CHECK to yes
warn: or pass `-y' to ignore all ignorable checks.
error: cannot install while Rust is installed

解決方法:
這個錯誤是因為系統中已經通過包管理器(如 aptyum 等)安裝了 Rust,而 rustup 檢測到了現有安裝,為了避免環境衝突而終止了安裝。解決方法如下:
為了讓 rustup 成為主要的 Rust 工具鏈管理器,建議先卸載系統預裝的 Rust:

sudo dnf remove rust cargo

卸載完成後,重新運行 rustup 安裝腳本。

4 格式化輸出

在 Rust 中,println!("{:?}", other); 是一個用於打印變量 other 調試信息的宏調用,其中 {:?} 是格式化佔位符,對應 Debug 格式化輸出

4.1.1 核心作用

  • {:?} 要求被打印的類型實現了標準庫的 std::fmt::Debug trait,該 trait 用於提供類型的調試友好格式(通常包含詳細的內部結構)。
  • 與 {}(對應 Display trait)不同,Debug 輸出更偏向開發者調試,格式可能更冗長(例如包含字段名、引號等),且通常由編譯器自動派生(通過 #[derive(Debug)]),無需手動實現。
  • {:?} 的變體 {:#?} 會生成帶縮進的多行格式,適合複雜結構(如嵌套的結構體、長列表):

5 所有權

在Rust中,由Copy trait來區分值語義和引用語義。與此同時,Rust也引入了新的語義:複製(Copy)語義和移動(Move)語義。複製語義對應值語義,移動語義對應引用語義。

Rust的借用檢查器(borrow checker),借用檢查器會檢查所有數據訪問是否合法。借用檢查依賴於3個緊密關聯的概念:所有權、生命週期和借用。

  • 所有權(ownership)是一個引申而來的比喻,在Rust中,所有權與針對不再需要的值的清理有關。
  • 值的生命週期是一個時間段,在此時間段內對該值的訪問是有效的行為。
  • 借用一個值意味着要訪問它。

所有權的特點:

  1. 控制資源(不僅僅是內存)的釋放
  2. 出借所有權,包括不可變(共享)和可變(獨佔)的
    1. 通過使用&操作完成所有權租借
    2. 在不可變借用期間,所有者不能修改資源,也不能在進行可變借用
    3. 在可變借用期間,所有者不能訪問資源,並且也不能再出借所有權
    4. 不可變借用可以出借多次,因為他不能修改內存數據;可變借用只能出借一次,否則難以預料數據何時何地被修改。
  3. 轉移所有權

生命週期參數的目的是幫助借用檢查器驗證合法的引用,消除懸垂指針

  • 借用的生命週期不能長於出借方的生命週期
  • 結構體實例的生命週期應短於或等於任意一個成員的生命週期

省略生命週期參數

  1. 每個輸入位置上省略的生命週期都將成為一個不同的生命週期參數
  2. 如果只有一個輸入生命週期的位置(不管是否忽略),則該生命週期都將分配給輸出生命週期
  3. 如果存在多個輸入生命週期的位置,但是其中包含着&self或&mut self,則self的生命週期都將分配給輸出生命週期

對於Box<T>類型來説,如果包含的類型T屬於複製語義,則執行按位複製;如果屬於移動語義,則移動所有權

6 類型

6.1 ?Sized

在 Rust 中,?Sized 是一個用於 trait bound 的特殊標記,用於表示“允許類型不實現 Sized trait”。要理解它,首先需要了解 Sized trait 本身:

6.1.1 Sized trait 是什麼?

Sized 是 Rust 中的一個自動實現的 trait,用於標記“在編譯時已知大小的類型”(例如 i32String、自定義結構體等)。

  • 對於這類類型,編譯器知道它們在內存中佔據的精確大小,因此可以直接在棧上分配,也能作為函數參數/返回值直接傳遞。
  • 反之,動態大小類型(DST,Dynamically Sized Type) 則不實現 Sized,例如:
    • 切片 [T](長度未知,需通過 &[T] 等指針間接使用);
    • trait 對象(如 dyn Trait,具體類型大小未知);
    • 字符串字面量的底層類型 str(長度未知,需通過 &str 使用)。

?Sized 的作用:放寬 Sized 限制
Rust 中,泛型默認隱含 Sized 約束。例如:

fn foo<T>(x: T) { ... }
// 等價於
fn foo<T: Sized>(x: T) { ... }

這意味着泛型 T 只能接受編譯時大小已知的類型(Sized 類型)。

?Sized 的作用是取消這種默認約束,允許泛型接受“可能不實現 Sized 的類型”。例如:

fn bar<T: ?Sized>(x: &T) { ... }

這裏 T 可以是 Sized 類型(如 i32),也可以是動態大小類型(如 strdyn Trait)。

6.1.2 使用場景

?Sized 通常用於需要處理動態大小類型的場景,常見情況:

  • 接受 trait 對象
    trait 對象(dyn Trait)是 DST,因此泛型需要 ?Sized 才能接受它:

    trait MyTrait { fn do_something(&self); }
    
    // 允許 T 為 dyn MyTrait(DST)
    fn call_trait<T: MyTrait + ?Sized>(x: &T) {
        x.do_something();
    }
    
    // 使用:可以傳入任何實現 MyTrait 的類型的引用,或 trait 對象
    let obj: &dyn MyTrait = &SomeType;
    call_trait(obj); // 合法
    
  • 處理切片或字符串
    直接使用 [T]str 時(通常通過引用):

    // 接受 str(DST)的引用
    fn print_str<T: ?Sized>(s: &T) where T: AsRef<str> {
        println!("{}", s.as_ref());
    }
    
    print_str("hello"); // 字符串字面量是 &str,底層是 str(DST)
    
  • 定義容納 DST 的類型
    例如自定義智能指針時,指向 DST:

    struct MyBox<T: ?Sized>(*const T);
    
    impl<T: ?Sized> MyBox<T> {
        fn new(x: &T) -> Self {
            MyBox(x as *const T)
        }
    }
    

6.1.3 注意點

  • ?Sized 僅用於泛型約束,不能直接修飾具體類型。
  • 由於 DST 無法在棧上直接存儲或作為值傳遞,使用 ?Sized 的泛型通常需要通過引用(&T指針(如 Box<T>Rc<T> 間接操作。
  • ?Sized 是“允許不 Sized”,而非“必須不 Sized”,因此仍能接受 Sized 類型。

7 log

RUST_LOG 是 Rust 生態中用於控制 日誌輸出 的環境變量,主要配合 Rust 的日誌庫(如 logtracing)使用,用於動態調整日誌的 級別模塊範圍輸出內容,無需修改代碼即可靈活控制程序的日誌行為。
log是Rust 生態中最基礎、應用最廣泛的 日誌抽象庫(crate),它本身不直接實現日誌的輸出功能,而是定義了一套統一的日誌接口(如日誌級別、宏定義),讓其他庫或應用可以基於這套接口實現日誌記錄,同時保證不同日誌實現之間的兼容性

  1. 提供統一的日誌接口
    定義了 trace!debug!info!warn!error! 等日誌宏,以及 LogLevelMetadata 等核心 trait 和枚舉,讓開發者可以用一致的方式編寫日誌代碼,無需關心底層如何輸出(如打印到終端、寫入文件、發送到日誌服務器等)。
  2. 解耦日誌生產與消費
    庫開發者只需依賴 log 庫編寫日誌(如 info!("初始化完成")),而應用開發者可以自由選擇日誌的實現方式(如 env_loggertracingfern 等),兩者通過 log 的接口對接,避免了庫與特定日誌實現的強耦合。
  3. 常用搭配的日誌實現庫
    log 庫本身不輸出日誌,必須配合具體的 “日誌實現庫” 才能生效,常見的有:
    • env_logger:通過 RUST_LOG 環境變量控制日誌輸出,適合命令行工具和開發調試。
    • tracing:更強大的日誌和追蹤庫,支持結構化日誌、跨度(span)追蹤,適合複雜應用和分佈式系統。
    • fern:支持將日誌輸出到文件、終端等多種目標,可自定義格式和滾動策略。
    • simple_logger:簡單輕量的實現,適合快速上手,無需複雜配置。

7.1 日誌級別(從低到高)

Rust 日誌庫定義了 5 個標準級別(級別越高,輸出日誌越少):

  • trace:最詳細的調試信息(如函數調用參數、循環步驟),通常用於開發階段細粒度調試。
  • debug:調試信息(如關鍵流程節點、變量值),適合開發和測試環境。
  • info:普通運行信息(如程序啓動、任務完成),生產環境常用。
  • warn:警告信息(如不影響運行的異常情況,如“配置項缺失,使用默認值”)。
  • error:錯誤信息(如功能失敗、資源不可用),必須關注的問題。

規則:設置某一級別後,會輸出該級別及所有更高級別的日誌。例如,RUST_LOG=info 會輸出 infowarnerror 級別的日誌。

7.2 基本設置方法

7.2.1 全局設置日誌級別

通過 RUST_LOG=<級別> 控制全局日誌輸出:

# 只輸出 error 及以上級別日誌(最簡潔)
RUST_LOG=error cargo run

# 輸出 info 及以上級別(info, warn, error)
RUST_LOG=info ./my_rust_program

# 輸出 debug 及以上級別(開發調試常用)
RUST_LOG=debug cargo test

# 輸出所有級別(包括 trace,最詳細)
RUST_LOG=trace ./my_rust_program

7.2.2 限定模塊/ crate 的日誌範圍

通過 RUST_LOG=<模塊路徑>=<級別> 只輸出特定模塊的日誌,避免全局日誌冗餘:

# 只輸出 my_project 中 network 模塊的 debug 級別日誌
RUST_LOG=my_project::network=debug cargo run

# 輸出 tokio 庫的 info 日誌 + 自己代碼的 debug 日誌
RUST_LOG=tokio=info,my_project=debug ./my_program

# 禁用某個模塊的日誌(設置為 off)
RUST_LOG=my_project::legacy=off ./my_program
  • 模塊路徑對應代碼中的 mod 結構(如 crate::utils::file)。
  • 可以指定第三方 crate 的名稱(如 tokiohyper),控制其日誌輸出。

7.2.3 組合設置(多模塊 + 不同級別)

用逗號分隔多個規則,實現精細化控制:

# 全局 info 級別,但 network 模塊用 debug,tokio 庫用 warn
RUST_LOG=info,my_project::network=debug,tokio=warn ./my_program

7.2.4 在代碼中讀取 RUST_LOG

需配合日誌庫(如 log + env_logger)在程序中初始化日誌系統,才能讓 RUST_LOG 生效。示例:

  1. Cargo.toml 中添加依賴:

    [dependencies]
    log = "0.4"          # 日誌基礎庫
    env_logger = "0.9"   # 解析 RUST_LOG 的庫
    
  2. 在代碼中初始化:

    use log::{info, debug, error};
    
    fn main() {
        // 初始化日誌系統,讀取 RUST_LOG 環境變量
        env_logger::init();
    
        info!("程序啓動");
        debug!("配置文件路徑: ./config.toml");  // 僅 RUST_LOG>=debug 時輸出
        error!("數據庫連接失敗");
    }
    
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.