歡迎踏上Rust學習之旅!
第一周:奠定基礎 (Week 1: Laying the Foundation)
第1天:環境搭建與 “Hello, World!”
核心概念: 安裝Rust工具鏈 (
rustup
),它包含了編譯器rustc
和包管理器Cargo
。Cargo是你的好朋友,用于創建項目、構建、測試和管理依賴。代碼示例:
main.rs
文件內容// main函數是每個可執行Rust程序的入口點 fn main() {// println! 是一個宏,用于將文本打印到控制臺println!("Hello, world!"); }
編程練習:
訪問
rustup.rs
并按照說明安裝Rust。打開終端,運行
cargo new hello_rust
創建一個新項目。進入
hello_rust/src/main.rs
文件,修改打印內容為你自己的問候語。在項目根目錄運行
cargo run
,看到輸出。
第2天:變量、可變性與數據類型初探
核心概念: Rust中的變量默認是不可變的(immutable)。使用
mut
關鍵字可以使其變為可變的。Rust是靜態類型語言,但編譯器通常可以推斷出你想要的類型。代碼示例:
fn main() {// 默認不可變let x = 5;println!("The value of x is: {}", x);// x = 6; // <-- 這行會編譯錯誤!// 使用 mut 關鍵字使其可變let mut y = 10;println!("The initial value of y is: {}", y);y = 20;println!("The new value of y is: {}", y); }
編程練習: 創建一個程序,聲明一個不可變變量
spaces
并賦值為一個字符串。再聲明一個同名的可變變量,記錄字符串的長度。這種“遮蔽”(Shadowing)是允許的。
第3天:標量數據類型 (Scalar Types)
核心概念: 了解Rust的四種基本標量類型:整數、浮點數、布爾值和字符。
代碼示例:
fn main() {// 整數 (Integer)let apples: i32 = 5; // 帶類型標注let bananas = 10u64; // 帶類型后綴// 浮點數 (Floating-Point)let pi: f64 = 3.14159;// 布爾值 (Boolean)let is_rust_fun: bool = true;// 字符 (Character) - 使用單引號let heart_eyed_cat = '😻';println!("Apples: {}, Bananas: {}, Pi: {}", apples, bananas, pi);println!("Is Rust fun? {}, Look: {}", is_rust_fun, heart_eyed_cat); }
編程練習: 編寫一個程序,進行一些基本的數學運算,比如整數加法和浮點數除法,并將結果打印出來。
第4天:函數 (Functions)
核心概念: 使用
fn
關鍵字定義函數。函數可以有參數和返回值。表達式的最后一個值若不帶分號,則會作為函數的返回值。代碼示例:
fn main() {let sum = add_five(10);println!("10 + 5 = {}", sum); }// 定義一個函數,接收一個 i32 參數,返回一個 i32 fn add_five(x: i32) -> i32 {x + 5 // 這是一個表達式,它的值將作為函數的返回值 }
編程練習: 編寫一個名為
celsius_to_fahrenheit
的函數,它接收一個f64
類型的攝氏溫度作為參數,返回轉換后的華氏溫度(公式:F=Ctimes1.8+32)。在main
函數中調用它并打印結果。
第5天:控制流 (Control Flow)
核心概念: 學習
if-else
表達式和三種循環:loop
,while
,for
。代碼示例:
fn main() {let number = 6;if number % 4 == 0 {println!("number is divisible by 4");} else if number % 3 == 0 {println!("number is divisible by 3");} else {println!("number is not divisible by 4 or 3");}// for 循環for i in 1..=5 { // `..=` 表示包含5println!("Looping: {}", i);} }
編程練習: 使用
loop
循環,在循環體中將一個計數器加一,當計數器達到10時,使用break
關鍵字并從循環中返回計數器的兩倍值。
第6天:復合類型:元組(Tuple)和數組(Array)
核心概念: 元組是固定長度、可包含多種類型的集合。數組是固定長度、所有元素必須是相同類型的集合。
代碼示例:
fn main() {// 元組 (Tuple)let user_info: (&str, i32, bool) = ("Alice", 30, true);// 解構元組let (name, age, _is_active) = user_info;println!("User: {}, Age: {}", name, age);// 通過索引訪問println!("First element: {}", user_info.0);// 數組 (Array)let months: [&str; 3] = ["January", "February", "March"];println!("The first month is: {}", months[0]); }
編程練習: 創建一個元組來存儲一個HTTP狀態(狀態碼
u16
,消息&str
),例如(200, "OK")
。然后創建一個包含5個浮點數的數組,計算并打印它們的平均值。
第7天:所有權 (Ownership)
核心概念: 內存由“所有者”管理。當所有者離開作用域,值被清理。值只能有一個所有者。賦值操作會發生“移動”(Move)。
代碼示例:
fn main() {// s1 擁有一個 Stringlet s1 = String::from("hello");// s1 的所有權被“移動”到 s2let s2 = s1;// println!("s1 is {}", s1); // <-- 這行會編譯錯誤!因為 s1 不再擁有數據println!("s2 is {}", s2); // s2 現在是所有者,可以被使用 }
編程練習: 創建一個
String
類型的變量s1
。將其賦值給s2
。然后嘗試打印s1
,仔細閱讀并理解編譯器關于“值被移動”(value moved)的錯誤信息。
第二周:深入所有權與結構化數據
第8天:引用 (References) 與借用 (Borrowing)
核心概念: 如果不想轉移所有權,可以創建值的“引用”,這被稱為“借用”。引用默認不可變。
&mut
創建可變引用。代碼示例:
fn main() {let s1 = String::from("hello");// calculate_length 函數“借用”了 s1,而不是獲取所有權let len = calculate_length(&s1);// 因為 s1 的所有權沒有被移動,所以在這里仍然可以訪問它println!("The length of '{}' is {}.", s1, len); }fn calculate_length(s: &String) -> usize {s.len() } // s 在這里離開作用域,但因為它不擁有所引用的數據,所以什么也不會發生
編程練習: 重寫第7天的練習。創建一個計算
String
長度的函數,該函數接收String
的引用&String
作為參數。在main
函數中調用此函數后,驗證原來的String
變量仍然可用。
第9天:切片 (Slices)
核心概念: 切片允許你引用集合中一部分連續的元素序列,它本身不持有所有權。
代碼示例:
fn main() {let sentence = String::from("hello world");let hello = &sentence[0..5]; // or &sentence[..5]let world = &sentence[6..11]; // or &sentence[6..]println!("First word: {}, Second word: {}", hello, world); }
編程練習: 編寫一個函數,它接收一個字符串切片
&str
,并返回它找到的第一個單詞的切片。提示:通過遍歷字符串中的字節來尋找空格。
第10天:結構體 (Structs)
核心概念: 使用
struct
關鍵字創建自定義的復合數據類型。代碼示例:
// 定義一個結構體 struct Rectangle {width: u32,height: u32, }fn main() {// 創建一個 Rectangle 實例let rect1 = Rectangle {width: 30,height: 50,};println!("The area of the rectangle is {} square pixels.",rect1.width * rect1.height); }
編程練習: 定義一個
User
結構體,包含username
(String
)、email
(String
) 和active
(bool
) 字段。在main
函數中,創建一個User
實例并打印其email
。
第11天:結構體上的方法 (Methods on Structs)
核心概念: 使用
impl
塊為結構體定義方法。方法的第一個參數通常是&self
、&mut self
或self
。代碼示例:
struct Rectangle {width: u32,height: u32, }// 為 Rectangle 實現方法 impl Rectangle {// `&self` 是 `self: &Rectangle` 的縮寫fn area(&self) -> u32 {self.width * self.height} }fn main() {let rect1 = Rectangle { width: 30, height: 50 };// 使用點號調用方法println!("The area is {}", rect1.area()); }
編程練習: 為第10天的
User
結構體實現一個deactivate
方法,它接收一個&mut self
,將active
字段設置為false
。
第12天:枚舉 (Enums) 與模式匹配 (Pattern Matching)
核心概念:
enum
允許你定義一個可以枚舉出不同可能值的類型。match
控制流運算符必須窮盡所有可能性。代碼示例:
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32), }fn process_message(msg: Message) {match msg {Message::Quit => println!("Quit"),Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),Message::Write(text) => println!("Text message: {}", text),Message::ChangeColor(r, g, b) => println!("Change color to R:{}, G:{}, B:{}", r, g, b),} }fn main() {process_message(Message::Write(String::from("hello")));process_message(Message::Quit); }
編程練習: 創建一個
TrafficLight
枚舉,包含Red
、Yellow
、Green
三個變體。編寫一個函數,接收一個TrafficLight
枚舉,使用match
返回每種燈需要等待的時間(u8
類型)。
第13天:Option
枚舉
核心概念:
Option<T>
用于處理可能為空的值,避免了null
帶來的問題。它有兩個變體:Some(T)
和None
。代碼示例:
fn find_division_result(numerator: f64, denominator: f64) -> Option<f64> {if denominator == 0.0 {None} else {Some(numerator / denominator)} }fn main() {let result1 = find_division_result(10.0, 2.0);let result2 = find_division_result(10.0, 0.0);match result1 {Some(value) => println!("Result 1: {}", value),None => println!("Result 1: Cannot divide by zero"),}// if let 是 match 的一種簡寫形式if let Some(value) = result2 {println!("Result 2: {}", value);} else {println!("Result 2: Cannot divide by zero");} }
編程練習: 編寫一個函數,接收一個整數數組的切片,如果切片不為空,則返回
Some
包含第一個元素的值,否則返回None
。在main
函數中使用match
來處理這個Option
結果。
第14天:包、Crate和模塊 (Packages, Crates, and Modules)
核心概念: 使用
mod
和use
來組織代碼,將代碼分割到不同文件和模塊中。代碼示例:
// 文件結構: // ├── src // │ ├── main.rs // │ └── front_of_house.rs// src/main.rs mod front_of_house; // 聲明 front_of_house 模塊,Rust會查找 front_of_house.rs// 使用 use 關鍵字將 hosting 引入作用域 use front_of_house::hosting;fn main() {hosting::add_to_waitlist(); }// src/front_of_house.rs // 模塊默認是私有的,需要用 pub 關鍵字使其公開 pub mod hosting {pub fn add_to_waitlist() {println!("Added to waitlist!");} }
編程練習: 將第11天的
User
結構體及其impl
塊移動到一個名為models.rs
的獨立文件中。然后在main.rs
中通過mod models;
和use models::User;
來使用它。
第三周:集合、錯誤處理與泛型
第15天:Vector (動態數組)
核心概念:
Vec<T>
是一種可增長的、存儲在堆上的集合類型。代碼示例:
fn main() {// 創建一個可變的 vectorlet mut v: Vec<i32> = Vec::new();// 添加元素v.push(5);v.push(6);v.push(7);// 訪問元素(安全的方式)match v.get(2) {Some(third) => println!("The third element is {}", third),None => println!("There is no third element."),}// 遍歷 vectorfor i in &v {println!("{}", i);} }
編程練習: 創建一個
Vec<i32>
,向其中添加數字1到5。然后遍歷這個vector,將每個元素乘以2,并將結果存儲在一個新的vector中。
第16天:字符串 (String)
核心概念:
&str
是字符串切片,String
是堆上分配、可變的字符串。代碼示例:
fn main() {let mut s = String::from("foo");s.push_str("bar");println!("{}", s); // "foobar"let s1 = String::from("Hello, ");let s2 = String::from("world!");// + 操作符會獲取 s1 的所有權let s3 = s1 + &s2; println!("{}", s3);// println!("{}", s1); // s1 在這里不再有效 }
編程練習: 創建一個空的
String
,然后使用push_str()
和push()
方法向其中添加文本和字符,最后將其與其他&str
拼接成一句話并打印。
第17天:哈希表 (Hash Maps)
核心概念:
HashMap<K, V>
用于存儲鍵值對。代碼示例:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);let team_name = String::from("Blue");// get 方法返回一個 Option<&V>let score = scores.get(&team_name).copied().unwrap_or(0);println!("Blue team score: {}", score);// 遍歷for (key, value) in &scores {println!("{}: {}", key, value);} }
編程練習: 創建一個
HashMap
來統計一段文本中每個單詞出現的次數。
第18天:錯誤處理與Result
核心概念:
Result<T, E>
用于處理可能失敗的操作。?
運算符可以簡化錯誤傳播。代碼示例:
use std::fs::File; use std::io::Read;// 函數返回一個 Result fn read_username_from_file() -> Result<String, std::io::Error> {let mut f = File::open("hello.txt")?; // `?` 如果是 Err,則直接返回 Errlet mut s = String::new();f.read_to_string(&mut s)?; // `?` 如果是 Err,則直接返回 ErrOk(s) // 如果成功,則返回 Ok(s) }fn main() {match read_username_from_file() {Ok(username) => println!("Username: {}", username),Err(e) => println!("Error reading file: {}", e),} }
編程練習: 編寫一個
safe_divide
函數,接收兩個f64
參數。如果除數為零,則返回一個Err
包含描述錯誤的字符串;否則返回Ok
包含除法結果。在main
中調用它并使用match
處理Result
。
第19天:泛型 (Generics)
核心概念: 泛型是具體類型或其他屬性的抽象替代,用于減少代碼重復。
代碼示例:
// 泛型結構體 struct Point<T> {x: T,y: T, }// 泛型函數 fn print_point<T: std::fmt::Display>(p: &Point<T>) {println!("Point is at ({}, {})", p.x, p.y); }fn main() {let integer_point = Point { x: 5, y: 10 };let float_point = Point { x: 1.0, y: 4.0 };print_point(&integer_point);print_point(&float_point); }
編程練習: 創建一個泛型函數
largest<T: PartialOrd>(list: &[T]) -> &T
,它可以找到任何實現了PartialOrd
trait(即可比較大小)的類型的切片中的最大元素。
第20天:Trait (特性)
核心概念: Trait類似于接口,定義了某種類型必須提供的方法簽名。
代碼示例:
// 定義一個 Trait pub trait Summary {fn summarize(&self) -> String; }pub struct Tweet {pub username: String,pub content: String, }// 為 Tweet 類型實現 Summary Trait impl Summary for Tweet {fn summarize(&self) -> String {format!("{}: {}", self.username, self.content)} }fn main() {let tweet = Tweet {username: String::from("horse_ebooks"),content: String::from("of course, as you probably already know, people"),};println!("1 new tweet: {}", tweet.summarize()); }
編程練習: 定義一個名為
CanSpeak
的trait,它有一個speak(&self) -> String
方法。為你之前創建的User
結構體和新創建的Cat
結構體實現這個trait。
第21天:生命周期 (Lifetimes)
核心概念: 生命周期是編譯器用來確保所有借用都有效的范圍。大多數情況編譯器可自動推斷,復雜時需手動標注。
代碼示例:
// 'a 是生命周期參數 // 它告訴 Rust,返回的引用的生命周期與 x 和 y 中較短的那個生命周期相關聯 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y} }fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");// result 的生命周期被限制在 string2 的生命周期內result = longest(string1.as_str(), string2.as_str());println!("The longest string is {}", result);}// println!("The longest string is {}", result); // <-- 這里會報錯,因為 string2 和 result 都已失效 }
編程練習: 編寫一個結構體
ImportantExcerpt<'a>
,它包含一個字段part
,類型為&'a str
。驗證你不能創建一個比它所引用的字符串活得更長的ImportantExcerpt
實例。
第四周:高級特性與并發
第22天:閉包 (Closures)
核心概念: 閉包是可以捕獲其環境的匿名函數。
代碼示例:
fn main() {let x = 4;// 這個閉包捕獲了環境中的 xlet equal_to_x = |z| z == x;let y = 4;assert!(equal_to_x(y));let v1 = vec![1, 2, 3];// map 方法接收一個閉包作為參數let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();println!("v2: {:?}", v2); // v2: [2, 3, 4] }
編程練習: 使用
find
方法和一個閉包,在一個Vec<i32>
中查找第一個大于10的數字。
第23天:迭代器 (Iterators)
核心概念: 迭代器是處理元素序列的一種懶惰(lazy)且高效的方式。
代碼示例:
fn main() {let v1 = vec![1, 2, 3, 4, 5];let iterator = v1.iter(); // 創建迭代器// 迭代器是懶惰的,需要消耗它們才能做事let total: i32 = iterator.sum(); // sum() 會消耗迭代器println!("Sum: {}", total);// 鏈式調用let v2: Vec<i32> = vec![1, 2, 3];let v3: Vec<_> = v2.iter().map(|x| x * 2).filter(|&x| x > 4).collect();println!("v3: {:?}", v3); // v3: [6] }
編程練習: 創建一個從1到100的數字vector。使用迭代器、
filter
和map
鏈式調用,找出所有能被3整除的數,將它們平方,然后使用sum
計算總和。
第24天:智能指針 (Box
, Rc
, RefCell
)
核心概念:
Box<T>
在堆上分配值;Rc<T>
允許多個所有者;RefCell<T>
在運行時檢查借用規則。代碼示例: (
Box
用于遞歸類型)// 使用 Box 來創建遞歸的 Cons List 類型 enum List {Cons(i32, Box<List>),Nil, } use List::{Cons, Nil};fn main() {// (1, (2, (3, Nil)))let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
編程練習: 使用
Rc<T>
來創建一個允許多個列表共享同一個尾部(另一個列表)的數據結構。
第25天:并發:線程 (Threads)
核心概念: 使用
thread::spawn
創建新線程。move
關鍵字常用于閉包中,以轉移所有權。代碼示例:
use std::thread; use std::time::Duration;fn main() {let handle = thread::spawn(|| {for i in 1..10 {println!("hi number {} from the spawned thread!", i);thread::sleep(Duration::from_millis(1));}});for i in 1..5 {println!("hi number {} from the main thread!", i);thread::sleep(Duration::from_millis(1));}// 等待子線程結束handle.join().unwrap(); }
編程練習: 創建一個新線程,它持有一個
Vec
的所有權并打印其中的元素。在主線程中嘗試訪問這個Vec
,觀察編譯錯誤。
第26天:并發:消息傳遞 (Message Passing)
核心概念: 使用通道(Channel)在線程間安全地傳遞消息,避免共享內存。
代碼示例:
use std::sync::mpsc; // multiple producer, single consumer use std::thread;fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let val = String::from("hi");tx.send(val).unwrap();// println!("val is {}", val); // val 的所有權已經轉移,這里會報錯});let received = rx.recv().unwrap();println!("Got: {}", received); }
編程練習: 創建一個通道。在一個子線程中,發送多條消息(比如數字1到5)。在主線程中,使用
for
循環來接收并打印所有消息。
第27天:并發:共享狀態 (Mutex
與Arc
)
核心概念:
Mutex<T>
提供互斥訪問。Arc<T>
是線程安全的引用計數指針,允許多個線程擁有同一個值的引用。代碼示例:
use std::sync::{Arc, Mutex}; use std::thread;fn main() {// 使用 Arc 來允許多個所有者,Mutex 來保證一次只有一個線程能修改值let counter = Arc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Arc::clone(&counter);let handle = thread::spawn(move || {// lock() 會阻塞當前線程,直到可以獲取鎖let mut num = counter.lock().unwrap();*num += 1;}); // 鎖在這里被釋放handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap()); // 最終結果是 10 }
編程練習: 重做以上練習,但這次每個線程將計數器增加2而不是1。驗證最終結果是20。
第28天:Cargo進階與生態系統
核心概念: 通過在
Cargo.toml
中添加依賴來使用crates.io
上的第三方庫。代碼示例:
// 1. 在 Cargo.toml 文件中添加: // [dependencies] // serde = { version = "1.0", features = ["derive"] } // serde_json = "1.0"// 2. 在 main.rs 中使用: use serde::{Serialize, Deserialize};#[derive(Serialize, Deserialize, Debug)] struct Point {x: i32,y: i32, }fn main() {let point = Point { x: 1, y: 2 };// 序列化為 JSON 字符串let serialized = serde_json::to_string(&point).unwrap();println!("serialized = {}", serialized);// 反序列化let deserialized: Point = serde_json::from_str(&serialized).unwrap();println!("deserialized = {:?}", deserialized); }
編程練習: 在你的項目中添加一個流行的第三方庫,比如
rand
,用它來生成一個隨機數并打印出來。
第29天:unsafe
Rust
核心概念:
unsafe
關鍵字讓你繞過編譯器的某些安全檢查,用于與非Rust代碼交互、或進行編譯器無法理解的底層優化等。代碼示例: (請謹慎使用)
fn main() {let mut num = 5;// 創建裸指針let r1 = &num as *const i32;let r2 = &mut num as *mut i32;// 解引用裸指針必須在 unsafe 塊中進行unsafe {println!("r1 is: {}", *r1);*r2 = 10; // 修改數據println!("r2 now points to: {}", *r2);} }
編程練習: (僅為理解)編寫一個
unsafe
塊,創建一個指向整數的裸指針,并解引用它來讀取值。思考在哪些情況下這個操作可能導致未定義行為。
第30天:下一步與項目構思
核心概念: 回顧所學,總結Rust的核心優勢。探索生態系統,如異步編程(
async/await
)、WebAssembly、嵌入式等。代碼示例: (異步代碼
async/await
概念)// 需要添加 tokio 依賴: `tokio = { version = "1", features = ["full"] }`#[tokio::main]async fn main() {println!("Hello");// .await 表示等待異步操作完成,但不會阻塞整個線程let result = fetch_data_from_db().await;println!("Data fetched: {}", result);}async fn fetch_data_from_db() -> String {// 模擬一個耗時的數據庫查詢tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;"Some data from database".to_string()}
編程練習: 為自己構思一個小的結業項目。例如:一個簡單的命令行待辦事項應用、一個TCP端口掃描器、一個簡單的Web服務器。寫下項目的功能需求和你計劃使用的crates,然后開始動手吧!
祝您學習愉快,編程順利!歡迎來到Rust的世界!