70 ngày học Rust
Tuần 1:
Ngày 1:
Giới thiệu về Rust, tại sao nên chọn Rust
Cài đặt Rust và công cụ (rustup, cargo)
Chương trình Rust đầu tiên ("Hello, world!")
Bài tập:
Viết chương trình in ra thông tin cá nhân của bạn (họ tên, tuổi, sở thích).
Tạo một chương trình tính toán tổng, hiệu, tích, thương của hai số nhập từ bàn phím.
Tìm hiểu về các kiểu dữ liệu cơ bản trong Rust (số nguyên, số thực, boolean, ký tự) và viết chương trình minh họa cách sử dụng chúng.
Ngày 2:
Các khái niệm cơ bản: Biến, kiểu dữ liệu, hàm, điều khiển luồng (if/else, loop)
Bài tập:
Viết hàm tính giai thừa của một số nguyên dương.
Viết chương trình kiểm tra một số có phải là số nguyên tố hay không.
Tạo một chương trình chuyển đổi nhiệt độ từ độ C sang độ F và ngược lại.
Ngày 3:
Ownership (quyền sở hữu): stack vs heap, quy tắc sở hữu, di chuyển (move)
Copytrait: kiểu nào Copy, kiểu nào không, và tại saoBài tập:
Viết chương trình minh họa quy tắc sở hữu và sự khác biệt giữa di chuyển và mượn.
Tạo một hàm nhận vào một chuỗi và trả về một chuỗi mới được đảo ngược thứ tự các ký tự.
Giải thích tại sao đoạn mã sau không biên dịch được và cách sửa:
fn main() { let s1 = String::from("hello"); let s2 = s1; println!("{}", s1); }
Ngày 4:
References (tham chiếu) và Borrowing (mượn): tham chiếu không thay đổi (
&) và tham chiếu có thể thay đổi (&mut)Quy tắc borrow checker: "1 mutable HOẶC nhiều immutable", đọc hiểu lỗi compiler thay vì đoán mò
Bài tập:
Viết 3 đoạn code cố tình vi phạm borrow checker (dangling reference, double mutable borrow, use-after-move), giải thích lỗi compiler đưa ra và sửa từng đoạn.
Viết một hàm nhận
&mut Vec<i32>và nhân đôi từng phần tử tại chỗ (không tạo vector mới).Giải thích vì sao Rust không cho phép vừa giữ
&mutvừa giữ&tới cùng dữ liệu, bằng một ví dụ thực tế có thể gây bug ở ngôn ngữ khác.
Tuần 2:
Ngày 5:
Lifetime: vì sao cần lifetime, 3 quy tắc lifetime elision compiler tự suy luận
Lifetime tường minh trong hàm và trong struct chứa reference
'staticlifetime: khi nào thật sự cần, khi nào là dấu hiệu thiết kế saiBài tập:
Viết hàm
longest<'a>(x: &'a str, y: &'a str) -> &'a strtrả về chuỗi dài hơn, đảm bảo tính hợp lệ của tham chiếu.Tạo struct
Important<'a>chứa một&'a str, viết method sử dụng lifetime đó.Tìm và sửa 2 lỗi lifetime compiler thường gặp (ví dụ trả về reference tới biến local).
Ngày 6:
Slice Type: làm việc với một phần của mảng hoặc chuỗi (
&[T],&str)Bài tập:
Viết hàm tính tổng các phần tử trong một slice của số nguyên.
Tạo một hàm tìm kiếm vị trí của một phần tử trong một slice.
Viết chương trình chia một chuỗi thành các từ và in ra từng từ trên một dòng.
Ngày 7:
Struct: định nghĩa struct, khởi tạo struct, struct update syntax, tuple struct, unit struct
Bài tập:
Tạo một struct
Pointđể biểu diễn một điểm trong mặt phẳng 2 chiều.Viết hàm tính khoảng cách giữa hai điểm.
Tạo một struct
Rectanglevới các trường chiều rộng và chiều cao. Viết hàm tính diện tích và chu vi của hình chữ nhật.
Ngày 8:
Method Syntax: định nghĩa các phương thức cho struct, associated function (
Self::new)Bài tập:
Thêm các phương thức
areavàperimetervào structRectangle.Tạo một struct
Circlevà viết phương thức tính diện tích và chu vi của hình tròn.Viết một chương trình quản lý danh sách sinh viên, sử dụng struct để lưu trữ thông tin sinh viên và các phương thức để thêm, sửa, xóa sinh viên.
Tuần 3:
Ngày 9:
Enum: định nghĩa enum, các biến thể (variant) có mang dữ liệu
Pattern Matching:
match(exhaustive),if let,while let,matches!Option<T>: vì sao Rust không cónullBài tập:
Tạo một enum
Coinvới các biến thểPenny,Nickel,Dime,Quarter. Viết hàm chuyển đổi giá trị củaCoinsang số cent tương ứng.Xây dựng một chương trình máy tính đơn giản sử dụng enum để biểu diễn các phép toán và
matchđể thực hiện các phép tính.Viết một state machine nhỏ bằng enum (ví dụ trạng thái đơn hàng:
Pending/Shipped/Cancelled) với hàm chuyển trạng thái hợp lệ.
Ngày 10:
Smart Pointers cơ bản:
Box<T>(heap allocation, recursive type, trait object)DerefvàDroptrait: cơ chế đứng sau smart pointer và RAIIBài tập:
Cài đặt một
cons list(linked list kiểu Lisp) bằngBox.Tự implement một smart pointer tối giản (
MyBox<T>) cóDeref.Viết một struct tự implement
Drop, in log khi giá trị bị hủy, giải thích thứ tự drop khi có nhiều biến.
Ngày 11:
Rc<T>(single-thread) vsArc<T>(multi-thread): reference counting, ownership chia sẻWeak<T>: phá vòng lặp tham chiếu (reference cycle), tránh memory leakBài tập:
Dựng cấu trúc cây (tree) có node cha/con dùng
Rccho con vàWeakcho cha.Tạo một ví dụ có reference cycle bằng
Rc<RefCell<T>>không dùngWeak, quan sát memory leak, rồi sửa bằngWeak.Viết chương trình đếm số lượng owner hiện tại của một
RcbằngRc::strong_count.
Ngày 12:
Interior Mutability:
Cell<T>,RefCell<T>- mutate dữ liệu qua immutable reference, borrow checking lúc runtimePattern
Rc<RefCell<T>>: shared mutable state - pattern cực kỳ phổ biến trong Rust thực tếBài tập:
Cài một "shared counter" có nhiều owner (
Rc) cùng sửa được giá trị (RefCell).Viết một đoạn code cố tình gây
already borrowedpanic lúc runtime vớiRefCell, giải thích tại sao đây là đánh đổi hợp lý so với static borrow checker.So sánh
Cell<T>vàRefCell<T>: khi nào dùng cái nào (gợi ý:Cellcho kiểuCopy,RefCellcho kiểu phức tạp hơn).
Tuần 4:
Ngày 13:
Generic Types: viết code tổng quát, làm việc với nhiều kiểu dữ liệu
Traits: định nghĩa hành vi chung cho các kiểu dữ liệu
Generic vs Trait Object (
impl Traitvsdyn Trait): static dispatch vs dynamic dispatch, đánh đổi hiệu năng/binary sizeBài tập:
Viết một hàm generic tính tổng các phần tử trong một slice hoặc vector.
Tạo một trait
Summaryvới phương thứcsummarizevà implement cho các kiểu dữ liệuNewsArticlevàTweet.Viết
Vec<Box<dyn Summary>>chứa nhiều loại khác nhau, in tóm tắt của từng phần tử; so sánh với cách viết dùng generic + trait bound.
Ngày 14:
Associated types (
Iterator::Item) vs generic type parameter: khi nào dùng cái nàoSupertrait, default method, operator overloading (
std::ops)Bài tập:
Tự định nghĩa một trait có associated type (ví dụ trait
Containervớitype Item).Implement
Addcho structPointđể hai điểm cộng được bằng toán tử+.Viết một trait
Animalvới default methoddescribe(), và một supertrait yêu cầu implementstd::fmt::Display.
Ngày 15:
Memory Layout & Type System:
size_of::<T>(), alignment, paddingrepr(C),repr(transparent),repr(packed)- khi nào cần kiểm soát layout thay vì để compiler tự sắp xếpDST (Dynamically Sized Type) và ZST (Zero Sized Type); thin pointer vs fat pointer; vtable và layout của trait object
Bài tập:
Dùng
std::mem::size_ofvàstd::mem::align_ofin ra kích thước/alignment của nhiều struct khác nhau, thử đổi thứ tự field và quan sát padding thay đổi thế nào.Giải thích và minh họa bằng code vì sao
&strlà fat pointer(ptr, len)còn&i32là thin pointer, dùngsize_of::<&str>()vssize_of::<&i32>()để chứng minh.Viết một ví dụ
&dyn Trait, insize_of::<&dyn Trait>(), giải thích nó là(ptr, vtable ptr)và vtable chứa gì.
Ngày 16:
Design Pattern trong Rust: Builder Pattern, Newtype Pattern, Typestate Pattern
PhantomData, Extension Trait Pattern (mở rộng hành vi cho type có sẵn qua trait riêng)Bài tập:
Viết một
struct RequestBuildertheo Builder Pattern (method chain trả vềSelf) để build một HTTP request giả lập.Dùng Newtype Pattern bọc
StringthànhEmail(String)với validation khi khởi tạo, giải thích lợi ích so với dùngStringtrần.Viết một ví dụ Typestate Pattern đơn giản (ví dụ
Door<Open>/Door<Closed>là 2 kiểu khác nhau ở compile-time), dùngPhantomDatađể đánh dấu trạng thái.
Tuần 5:
Ngày 17:
Trait Coherence & Orphan Rule: chỉ được
impl Trait for Typenếu bạn sở hữu trait HOẶC type đó - lý do rule này tồn tại (tránh xung đột impl giữa các crate)Object Safety: điều kiện để một trait dùng được như
dyn Trait(không có generic method, không trảSelftheo giá trị...)Bài tập:
Viết một đoạn code cố tình vi phạm orphan rule (impl một trait ngoài crate cho một type ngoài crate), đọc lỗi compiler và giải thích lý do rule tồn tại.
Viết một trait có generic method, thử dùng nó như
Box<dyn Trait>, quan sát lỗi "not object safe", sửa bằng cách tách trait hoặc chuyển sang dùng generic + trait bound.So sánh
ClonevàDisplay/Debugtrong std: giải thích vì saoClonekhông object-safe (gợi ý: trảSelftheo giá trị) còn hai trait kia thì có.
Ngày 18:
Variance: covariance, contravariance, invariance - vì sao
&'static strcoerce được thành&'a strnhưng&mut Tthì invariant theoTPhantomData<T>: đánh dấu ownership/variance "ảo" khi struct không thực sự chứaT(thường gặp khi làm việc với raw pointer)Auto trait:
Send,Sync,Unpin,UnwindSafe- trait được compiler tự động implement dựa trên cấu trúc bên trong, không cần khai báo thủ côngBài tập:
Viết ví dụ minh họa vì sao
&'a Tlà covariant theo'anhưng&'a mut Tlà invariant, bằng một đoạn code sẽ gây lỗi thực tế nếu Rust cho phép covariant với&mut.Viết một struct chứa raw pointer
*const Tnhưng cần thêmPhantomData<T>để compiler suy luận đúng variance/ownership, giải thích nếu thiếuPhantomDatathì sai ở đâu.Viết một struct chứa
Rc<T>bên trong, giải thích tại sao compiler tự suy ra struct đó!Send; thử ép nó thànhSendbằngunsafe impl Send for ...và giải thích rõ rủi ro khi làm vậy.
Ngày 19:
Error Handling: khái niệm lỗi trong lập trình
panic!: cách xử lý lỗi không thể khôi phụcBài tập:
Viết chương trình cố tình gây ra lỗi
panic!và quan sát kết quả.Tìm hiểu về backtrace và cách nó giúp gỡ lỗi khi xảy ra
panic!.Viết một hàm tính căn bậc hai của một số, sử dụng
panic!để xử lý trường hợp số âm.
Ngày 20:
Error Handling có thể khôi phục bằng
Result, các phương thứcunwrap,expect,map,and_then, toán tử?Error handling production-grade:
thiserror(custom error type gọn gàng),anyhow(xử lý lỗi tầng application),Fromtrait để auto-convert lỗiBài tập:
Viết lại hàm tính căn bậc hai ở ngày 19 sử dụng
Resultđể xử lý lỗi.Viết một module đọc file cấu hình, dùng
thiserrorđịnh nghĩaConfigErrorvới các variant rõ ràng (NotFound,ParseError,InvalidValue), propagate lỗi bằng?.Viết một hàm ở tầng "application" dùng
anyhow::Resultgọi tới nhiều hàm có kiểu lỗi khác nhau, so sánh trải nghiệm với cách dùngthiserrorở ngày trước.
Tuần 6:
Ngày 21:
Threads:
std::thread::spawn,JoinHandleSendvàSync: hai trait quyết định cái gì được chia sẻ giữa thread, compiler enforce tại compile-timeBài tập:
Chia một bài toán tính tổng lớn (ví dụ tổng 100 triệu số) thành nhiều thread, so sánh thời gian chạy với bản single-thread.
Viết một đoạn code cố tình vi phạm
Send/Sync(ví dụ chia sẻRcgiữa các thread) và quan sát lỗi compiler.Dùng
JoinHandleđể thu thập kết quả trả về từ nhiều thread vào mộtVec.
Ngày 22:
Chia sẻ state giữa threads:
Mutex<T>,RwLock<T>, patternArc<Mutex<T>>Deadlock: nguyên nhân và cách tránh
Bài tập:
Cài bộ đếm dùng chung giữa 10 thread bằng
Arc<Mutex<i32>>.Tạo một deadlock có chủ đích (ví dụ hai thread khóa hai
Mutextheo thứ tự ngược nhau) rồi sửa.So sánh
MutexvàRwLock: viết ví dụ nhiều reader/một writer dùngRwLock.
Ngày 23:
Message passing:
std::sync::mpscchannelSo sánh triết lý "share memory by communicating" (channel) vs "communicate by sharing memory" (Mutex)
Bài tập:
Xây pipeline producer-consumer đơn giản bằng channel (1 producer, nhiều consumer).
Viết chương trình dùng nhiều
Sender(clone channel) gửi dữ liệu từ nhiều thread về mộtReceiver.So sánh cùng một bài toán (đếm ngược) được giải bằng
Arc<Mutex<T>>và bằng channel, nhận xét ưu nhược điểm.
Ngày 24:
Async/Await: khái niệm
Future, cách executor poll future, vì saoasync fnkhông tự chạy nếu không được await/spawnSo sánh async runtime với OS thread thật: khi nào dùng async (I/O-bound), khi nào dùng thread thật (CPU-bound)
Bài tập:
Viết một chương trình bất đồng bộ để tải xuống nhiều tệp tin cùng lúc.
Sử dụng async/await để tạo một chương trình kiểm tra trạng thái của nhiều website song song.
Tìm hiểu về các executor phổ biến trong Rust (Tokio, async-std) và giải thích vì sao
async fncần một runtime để chạy.
Tuần 7:
Ngày 25:
Vấn đề self-referential struct: vì sao Future cần
Pinđể an toàn khi bị movePin<T>và auto traitUnpin; ý tưởng "pin projection" (không cần thành thạopin-projectngay, chỉ cần hiểu bản chất)Send/Sync sâu hơn: cách compiler tự suy diễn dựa trên field bên trong, cách một type trở thành
!Send/!SyncBài tập:
Viết một struct self-referential đơn giản (một field trỏ tới field khác trong cùng struct) và giải thích vì sao nó không an toàn nếu struct bị move sau khi tạo con trỏ đó.
Tự implement
Futurecho một struct (không dùngasync fn), viếtfn poll(self: Pin<&mut Self>, ...), giải thích vì sao tham số làPin<&mut Self>chứ không phải&mut Self.Viết một type chứa
RefCell<T>và giải thích tại sao nóSendnhưng khôngSync; viết một type chứaMutex<T>và giải thích tại sao nó vừaSendvừaSync.
Ngày 26:
Tokio nâng cao cho production:
tokio::spawn,async_trait(trait có async method)tokio::select!(chạy nhiều future, lấy cái xong trước),join!/try_join!(chạy song song, đợi tất cả)Cancellation: điều gì xảy ra khi một
Futurebị drop giữa chừngBài tập:
Dùng
tokio::select!viết một tác vụ có timeout (race giữa tác vụ chính và mộttokio::time::sleep).Dùng
join!/try_join!gọi song song 3 tác vụ async độc lập, so sánh với gọi tuần tự bằng await từng cái.Viết một future bị drop giữa chừng (ví dụ do timeout ở bài trên), quan sát và giải thích code sau điểm await cuối cùng không bao giờ chạy - đây chính là cơ chế cancellation của Rust.
Ngày 27:
Packages và Crates: khái niệm package và crate trong Rust
Modules: cách tổ chức mã nguồn thành các module
Bài tập:
Tạo một package mới và tổ chức mã nguồn thành các module theo chức năng.
Viết một module
mathchứa các hàm tính toán cơ bản (cộng, trừ, nhân, chia).Tạo một module
utilschứa các hàm tiện ích (ví dụ: hàm in thông báo ra màn hình).
Ngày 28:
Sử dụng module: cách import,
pubvà kiểm soát khả năng truy cậpCargo workspace: quản lý nhiều crate trong một project - pattern phổ biến ở production (tách
core,cli,apithành các crate riêng)Bài tập:
Sử dụng các module đã tạo ở ngày 27 để xây dựng một chương trình tính toán đơn giản.
Thử nghiệm các cách khác nhau để import và sử dụng các item trong module, tìm hiểu quy tắc hiển thị (visibility).
Dựng một Cargo workspace gồm 2 crate (một
libchứa logic, mộtbindùng lại logic đó) dùng chungCargo.lock.
Tuần 8:
Ngày 29:
Collections: Vector - mảng động, các thao tác thêm/xóa/truy cập
Bài tập:
Tạo một vector chứa các số nguyên và thực hiện các thao tác thêm, xóa, truy cập phần tử.
Viết hàm tính tổng các phần tử trong một vector.
Tạo một vector chứa thông tin sinh viên (dùng struct
Student) và thực hiện các thao tác quản lý danh sách sinh viên.
Ngày 30:
Collections:
HashMap,HashSet,BTreeMap- khi nào dùng cái nào, độ phức tạp thời gianTrait
Hash,Eq,PartialEq,Ord,PartialOrd: vì sao dùng struct tự định nghĩa làm key củaHashMap/BTreeMapcần các trait này, và derive macro#[derive(Hash, Eq, PartialEq)]Bài tập:
Đếm tần suất xuất hiện của từng từ trong một văn bản bằng
HashMap.Dùng
HashSetđể loại bỏ phần tử trùng lặp trong một danh sách.Tạo một struct
UserIddùng làm key củaHashMap<UserId, User>: deriveHash,Eq,PartialEq, giải thích tại sao thiếu một trong ba là compiler báo lỗi; sau đó dùng struct đó làm key củaBTreeMap, derive thêmOrd,PartialOrd, quan sát thứ tự duyệt khácHashMapthế nào.
Ngày 31:
Collections: String - các thao tác nối chuỗi, cắt chuỗi, tìm kiếm, thay thế
Bài tập:
Viết chương trình nhập vào một chuỗi và in ra chuỗi đó dưới dạng chữ hoa.
Tạo một hàm kiểm tra xem một chuỗi có phải là palindrome (chuỗi đối xứng) hay không.
Xây dựng một chương trình đơn giản để mã hóa/giải mã một chuỗi sử dụng phương pháp Caesar cipher.
Ngày 32:
Iterator sâu:
Iteratortrait tự implement, lazy evaluation, zero-cost abstractionIntoIteratorvsIterator; sự khác biệt giữaiter(),iter_mut(),into_iter();FromIteratorvà cáchcollect()thực sự hoạt độngBài tập:
Sử dụng iterator để tính tổng các phần tử trong một vector.
Tạo một iterator tùy chỉnh để sinh ra các số Fibonacci (implement
Iteratorcho một struct).Viết một hàm nhận
impl IntoIterator<Item = i32>thay vì&Vec<i32>, gọi thử vớiVec, mảng, vàHashSetđể thấy tính tổng quát; sau đó tự implementFromIteratorcho một struct wrapper đểcollect()ra thẳng struct đó.
Tuần 9:
Ngày 33:
Closures: hàm ẩn danh, có thể lưu trữ trạng thái
Fn,FnMut,FnOnce: ba trait closure và sự khác biệtBài tập:
Viết một closure để sắp xếp một vector theo thứ tự tăng dần.
Sử dụng closure để lọc các phần tử chẵn trong một vector.
Viết một closure chỉ implement được
FnOnce(ví dụ move một giá trị không-Copy ra khỏi scope), giải thích tại sao nó không thể làFnhayFnMut.
Ngày 34:
Macro cơ bản:
macro_rules!, khi nào macro thay thế được cho hàm/generic, khi nào không nên dùng macroGiới thiệu proc macro (derive macro) ở mức đọc hiểu - vì sao
#[derive(Serialize)]hoạt động đượcBài tập:
Viết một
macro_rules!đơn giản (ví dụvec_of_strings!sinh raVec<String>từ danh sách literal).Viết một macro nhận số lượng tham số thay đổi (dùng
$(...)*) để tính tổng nhiều số.Đọc source code (hoặc tài liệu) của một derive macro trong
serde, tóm tắt bằng lời của bạn cách nó sinh code.
Ngày 35:
Unit Test: kiểm tra từng đơn vị mã nguồn (hàm, struct)
Cách viết và chạy unit test trong Rust
Bài tập:
Viết unit test cho hàm tính căn bậc hai đã tạo trước đó.
Viết unit test cho các phương thức của struct
Rectangle.Tìm hiểu về các thuộc tính (attribute) của test như
#[test],#[should_panic],#[ignore].
Ngày 36:
Integration Test: kiểm tra sự tương tác giữa các thành phần của chương trình
Bài tập:
Viết integration test cho chương trình dòng lệnh đã tạo trước đó.
Viết integration test cho chương trình đọc ghi tệp tin.
Tìm hiểu về cách sử dụng mock object trong integration test.
Tuần 10:
Ngày 37:
Testing nâng cao: property-based testing với
proptest/quickcheck- sinh input ngẫu nhiên thay vì viết case cố địnhBenchmark với
criterionBài tập:
Viết property test cho một hàm parse (ví dụ: parse rồi format lại phải ra kết quả gốc - round-trip test).
Viết property test đảm bảo hàm sắp xếp bạn tự viết luôn trả về danh sách đã sort đúng, với input ngẫu nhiên.
Benchmark hai cách implement khác nhau của cùng một hàm bằng
criterion, so sánh kết quả.
Ngày 38:
Profiling & Performance:
cargo flamegraphđể tìm hotspot, dùngperf(Linux) ở mức cơ bảnHeap profiling (ý tưởng chung: đo cấp phát bộ nhớ, phát hiện cấp phát thừa); đọc sâu hơn kết quả
criterion(variance, outlier)Bài tập:
Chạy
cargo flamegraphtrên một chương trình tính toán nặng (ví dụ tính số nguyên tố tới 10 triệu) và xác định hàm nào chiếm nhiều thời gian nhất.Tối ưu lại hàm hotspot tìm được ở bài 1, đo lại và so sánh flamegraph trước/sau.
Đọc báo cáo HTML mà
criterionsinh ra sau khi benchmark, giải thích ý nghĩa của biểu đồ phân phối thời gian chạy và các outlier.
Ngày 39:
Tooling production:
clippy(linter),rustfmt,cargo docCI cơ bản: chạy
cargo test,cargo clippy,cargo fmt --checktrong GitHub ActionsBài tập:
Chạy
cargo clippytrên toàn bộ code đã viết từ đầu khóa, sửa hết warning thay vì ignore.Viết doc comment (
///) cho các hàm public trong một module, generate bằngcargo doc --open.Viết file
.github/workflows/ci.ymlchạy test + clippy + fmt check, cấu hình để pipeline fail nếu có warning.
Ngày 40:
I/O Project: ôn tập input/output trong Rust, đọc ghi tệp tin, làm việc với stdin/stdout
Bài tập:
Viết chương trình đọc nội dung của một tệp tin và in ra màn hình.
Tạo một chương trình sao chép nội dung của một tệp tin sang một tệp tin khác.
Xây dựng một chương trình dòng lệnh đơn giản để quản lý danh sách công việc (todo list).
Tuần 11:
Ngày 41:
Thư viện chuẩn của Rust (std):
std::fs,std::io,std::collections,std::envBài tập:
Sử dụng
std::fsđể liệt kê các tệp tin trong một thư mục.Sử dụng
std::iođể đọc ghi dữ liệu từ bàn phím và màn hình.Sử dụng
std::envđể lấy các biến môi trường.
Ngày 42:
Serde: thư viện tuần tự hóa/giải tuần tự hóa dữ liệu - không chỉ JSON mà còn
toml(Cargo dùng định dạng này), và giới thiệu quayaml,bincode/msgpack(định dạng binary, hay dùng khi cần tốc độ hoặc kích thước nhỏ)Bài tập:
Sử dụng Serde để đọc và ghi dữ liệu JSON từ/vào tệp tin.
Đọc một file cấu hình dạng
tomlbằngserde+ cratetoml, so sánh trải nghiệm với đọc JSON.Serialize cùng một struct sang JSON và sang
bincode, so sánh kích thước output và giải thích khi nào nên chọn định dạng binary thay vì text.
Ngày 43:
Serde nâng cao: custom
Serialize/Deserialize,#[serde(rename)],#[serde(skip)],#[serde(default)]Xử lý dữ liệu JSON không đúng schema mong đợi (field thiếu, kiểu sai); dùng
chronohoặctimeđể serialize/deserialize ngày giờBài tập:
Viết struct có field optional dùng
#[serde(default)], test với JSON thiếu field đó.Viết struct có field kiểu
DateTime(dùngchrono), serialize/deserialize qua JSON, xử lý đúng timezone.Xử lý lỗi parse JSON một cách graceful (không panic), trả về
Resultcó thông tin lỗi rõ ràng.
Ngày 44:
Regex: thư viện làm việc với biểu thức chính quy
Bài tập:
Sử dụng Regex để kiểm tra định dạng email.
Sử dụng Regex để trích xuất thông tin từ một đoạn văn bản.
Tìm hiểu về các tính năng nâng cao của Regex (ví dụ: lookahead, lookbehind).
Tuần 12:
Ngày 45:
Clap: thư viện xây dựng giao diện dòng lệnh (CLI)
Bài tập:
Sử dụng Clap để tạo một chương trình CLI đơn giản.
Sử dụng Clap để tạo một chương trình CLI có nhiều subcommand.
Tìm hiểu về cách sử dụng Clap để xác thực đầu vào của người dùng.
Ngày 46:
Cargo sâu: cấu trúc
Cargo.toml, profile (dev/release), feature flags,build.rsBài tập:
Thêm feature flag tùy chọn cho một crate (ví dụ bật/tắt một module qua
[features]).Tìm hiểu và cấu hình custom profile trong
Cargo.toml(ví dụ[profile.release]vớilto = true).Viết một
build.rsđơn giản (ví dụ in ra thông tin build time).
Ngày 47:
Crates.io: kho lưu trữ thư viện Rust, semantic versioning
Cargo.lockvsCargo.tomltrong context production (khi nào commit lock file)Bài tập:
Tìm và sử dụng một thư viện từ crates.io để xử lý định dạng JSON.
Tìm và sử dụng một thư viện từ crates.io để sinh UUID (crate
uuid) làm ID cho một struct.Giải thích semantic versioning (
^1.2.3nghĩa là gì) và vì sao binary/service thường commitCargo.lockcòn library thì không.
Ngày 48:
Unsafe Rust: 5 "superpower" của
unsafe(deref raw pointer, gọi unsafe fn, truy cập mutable static, implement unsafe trait, access union field)MaybeUninit<T>: cấp phát bộ nhớ chưa khởi tạo an toàn (dùng trongVec,String...);NonNull<T>: raw pointer đảm bảo không null, nền tảng của nhiều cấu trúc dữ liệu tự cấp phát bộ nhớBài tập:
Viết đoạn code minh họa raw pointer (
*const T,*mut T) và giải thích vì sao nó không được borrow checker kiểm tra.Implement một
Vec-like structure tối giản dùngNonNull<T>để giữ con trỏ vàMaybeUninit<T>khi cấp phát vùng nhớ chưa ghi dữ liệu, giải thích từng chỗ cầnunsafevà invariant phải giữ để an toàn.Đọc source code phần cấp phát bộ nhớ bên trong
Vec<T>trong std, chỉ ra chỗ nào dùngNonNull/MaybeUninitvà giải thích vì sao.
Tuần 13:
Ngày 49:
Compiler pipeline ở mức tổng quan: source → AST → HIR → MIR → LLVM IR → machine code (không cần hiểu code compiler, chỉ cần hiểu vai trò từng bước)
Monomorphization: generic code được compiler sinh bản cụ thể cho từng kiểu dùng thực tế - lý do Rust đạt "zero-cost abstraction" nhưng đánh đổi bằng binary size (code bloat)
Công cụ quan sát thực tế:
cargo expand(xem macro/derive được mở rộng),rustc --emit=mir, godbolt.org (xem assembly sinh ra)Bài tập:
Dùng
cargo expandxem code thực tế sinh ra từ một#[derive(Debug)]và từ mộtmacro_rules!đã viết ở Ngày 34.Viết một hàm generic đơn giản, gọi với 3 kiểu khác nhau (
i32,f64,String), dùngcargo bloathoặc so sánh kích thước binary để quan sát monomorphization "nhân bản" code cho từng kiểu như thế nào.Dùng godbolt.org (hoặc
rustc --emit=mir) xem thử MIR/assembly của một hàm đơn giản; không cần hiểu 100% cú pháp, chỉ cần nhận diện được các basic block và nắm ý tưởng "MIR là bước trung gian gần Rust nhất trước khi hạ xuống LLVM IR".
Ngày 50:
FFI: gọi các hàm từ các ngôn ngữ khác (như C)
Bài tập:
Sử dụng FFI để gọi một hàm C đơn giản.
Sử dụng FFI để làm việc với một thư viện C phức tạp hơn (ví dụ: thư viện xử lý ảnh).
Tìm hiểu về cách xử lý các vấn đề về an toàn bộ nhớ khi sử dụng FFI.
Ngày 51:
Các công cụ hữu ích cho automation:
xsv(CSV),ripgrep(tìm kiếm),fd(thay thếfind),bat(xem file có syntax highlighting)Bài tập:
Sử dụng
xsvđể lọc, chuyển đổi, và phân tích dữ liệu CSV.Sử dụng
ripgrepđể tìm kiếm các đoạn mã trong dự án của bạn.Sử dụng
fdvàbatđể tìm và xem nội dung các tệp tin một cách nhanh chóng và thuận tiện.
Ngày 52:
Axum cơ bản: routing, handler, extractor (
Json,Path,Query,State)Bài tập:
Viết một REST API CRUD đơn giản (in-memory store) cho một resource, ví dụ "todo".
Thêm route với
Pathparam (ví dụGET /todos/:id) vàQueryparam (ví dụ filter theo trạng thái).Dùng
Stateđể chia sẻ mộtArc<Mutex<...>>store giữa các handler.
Tuần 14:
Ngày 53:
Middleware & error handling trong web service:
tower::Layer/tower::Service(nền tảng bên dưới Axum), chuẩn hóa lỗi quaIntoResponseBài tập:
Thêm middleware log mỗi request (method, path, thời gian xử lý) dùng
tower::Layer.Định nghĩa error type riêng cho API, implement
IntoResponseđể trả JSON error nhất quán (status code + message).Thêm middleware CORS cho API.
Ngày 54:
Database với
sqlx: compile-time checked query, connection pool, migrationDùng
uuidđể sinh khóa chính; giới thiệuDashMapnhư một lựa chọn thay thếArc<Mutex<HashMap>>khi cần concurrent map hiệu năng cao (ví dụ cache trong bộ nhớ)Bài tập:
Kết nối Postgres từ ứng dụng Axum, viết migration tạo bảng cho resource ở Ngày 52 với khóa chính kiểu
UUID.Thay in-memory store bằng CRUD thật qua
sqlx(tạo, đọc, sửa, xóa).Thêm một lớp cache đơn giản bằng
DashMaptrước khi query database, so sánh với cách dùngArc<Mutex<HashMap>>.
Ngày 55:
Testing cho web service: test handler với
tower::ServiceExt::oneshot, test database bằng transaction rollback patternBài tập:
Viết integration test cho flow tạo → đọc → sửa → xoá resource qua HTTP.
Viết test dùng transaction rollback để mỗi test chạy trên database sạch mà không cần dọn dẹp thủ công.
Viết test cho các trường hợp lỗi (input không hợp lệ, resource không tồn tại) và kiểm tra đúng status code trả về.
Ngày 56:
Config & environment: quản lý config qua env var/file (
dotenvy,configcrate), phân biệt config dev/staging/productionreqwest: gọi HTTP client tới service khác (thường cần khi service của bạn phải gọi ra ngoài)Bài tập:
Tách connection string database, port, log level... ra file
.envvà structConfigđọc từ env.Viết validation cho
Configkhi khởi động (fail-fast nếu thiếu biến bắt buộc).Dùng
reqwestgọi một API bên ngoài (ví dụ API thời tiết công khai) từ trong handler của Axum.
Tuần 15:
Ngày 57:
Structured logging:
tracingcrate - span, event, khác biệt vớilogcrate truyền thốngBài tập:
Thay toàn bộ
println!trong web service (Tuần 13–14) bằngtracing.Thêm structured field vào log (ví dụ
request_id,user_id) để dễ tìm kiếm sau này.Dùng
tracing::spanđể đo thời gian xử lý một request từ đầu đến cuối, log ra khi kết thúc.
Ngày 58:
Metrics & distributed tracing: expose Prometheus metrics, khái niệm distributed tracing (OpenTelemetry) ở mức đọc hiểu
Bài tập:
Expose endpoint
/metricscho web service, đếm tổng số request theo route.Đo và expose latency (p50/p95) của mỗi endpoint.
Thêm endpoint
/healthztrả về trạng thái sống của service và kết nối database.
Ngày 59:
Release build & optimization:
cargo build --release, LTO, codegen-units, strip symbol - đánh đổi thời gian build vs hiệu năng/binary sizeBài tập:
So sánh thời gian chạy và kích thước binary giữa build
debugvàrelease.Bật
lto = truevà giảmcodegen-units, đo lại thời gian build và hiệu năng.Dùng
strip = trueđể giảm kích thước binary, kiểm tra sự khác biệt.
Ngày 60:
Docker cho Rust: multi-stage build (build trong container lớn, chạy trong container tối giản như
distroless/scratch)Bài tập:
Viết
Dockerfilemulti-stage cho web service đã xây.So sánh kích thước image giữa bản build đơn giản và bản multi-stage tối ưu.
Chạy service trong container, kiểm tra log/healthcheck hoạt động đúng.
Tuần 16:
Ngày 61:
Cross-compilation & CI/CD:
cross, targetmuslcho container tối giản, pipeline test → build → docker build → pushBài tập:
Cross-compile ứng dụng sang target
x86_64-unknown-linux-musl.Viết pipeline CI/CD hoàn chỉnh: chạy test → build release → build docker image → (giả lập) push image.
Thêm bước cache dependency trong CI để giảm thời gian build.
Ngày 62 - Dự án cuối khóa: Nghiên cứu và thiết kế
Dự án: URL shortener service (REST API + Postgres + cache + observability + Docker)
Thiết kế schema database, các endpoint API, kiến trúc module (handler / service / repository)
Bài tập:
Vẽ sơ đồ kiến trúc tổng thể (API layer, service layer, database, cache).
Thiết kế schema bảng lưu URL gốc, mã rút gọn, thời gian tạo, số lượt click.
Lập danh sách endpoint cần có (tạo short URL, redirect, thống kê click).
Ngày 63 - Xây dựng API và database:
Implement các endpoint chính bằng Axum + sqlx theo thiết kế Ngày 62
Bài tập:
Implement endpoint tạo short URL (sinh mã ngẫu nhiên hoặc hash, lưu vào Postgres).
Implement endpoint redirect (
GET /:code) và tăng bộ đếm click.Thêm cache (
DashMaphoặc Redis) cho endpoint redirect để giảm tải database.
Ngày 64 - Testing & quan sát:
Viết test và thêm observability cho toàn bộ service
Bài tập:
Viết integration test cho flow tạo → redirect → xem thống kê.
Thêm
tracingstructured log và endpoint/metrics.Chạy
cargo clippy/cargo fmt --check, sửa hết warning.
Tuần 17:
Ngày 65 - Đóng gói và triển khai:
Docker hóa toàn bộ service (kèm Postgres qua
docker compose)Bài tập:
Viết
Dockerfilemulti-stage vàdocker-compose.yml(app + Postgres + Redis nếu dùng).Chạy
docker compose upvà kiểm tra service hoạt động end-to-end.Viết pipeline CI chạy test/clippy/fmt và build docker image khi push code.
Ngày 66 - Hoàn thiện và tài liệu hóa:
Viết README, dọn code, review lại toàn bộ dự án
Bài tập:
Viết README.md mô tả kiến trúc, cách chạy local, cách chạy test.
Review lại toàn bộ code bằng con mắt "người khác sẽ maintain": đặt tên rõ ràng, tách module hợp lý, xử lý lỗi nhất quán.
(Tùy chọn) Đăng repo lên GitHub, viết thêm phần "Lessons learned" ghi lại những gì khó nhất trong quá trình làm.
Ngày 67 - Đọc source code thư viện lớn (1/2):
Đọc source
serde: cách derive macro sinh code, ý tưởngVisitorpattern trong deserializeBài tập:
Đọc phần
derivecủaserde, đối chiếu với những gì đã học ở Ngày 34/49 về macro và monomorphization.Đọc ý tưởng
Visitortrong deserialize củaserde, giải thích bằng lời tại sao nó cần thiết cho các định dạng dữ liệu tự-mô-tả khác nhau.Viết một
Deserializeimplementation thủ công (không dùng derive) cho một struct đơn giản, dựa trên hiểu biết vừa đọc.
Ngày 68 - Đọc source code thư viện lớn (2/2):
Đọc source
axum: cáchRoutervàHandlerđược implement dựa trên traittower::ServiceBài tập:
Truy vết một request đi qua
axumtừ lúc router nhận vào tới lúc gọi handler, đối chiếu với kiến thứctower::Layer/Serviceđã học ở Ngày 53.Giải thích vì sao một hàm bất kỳ có chữ ký phù hợp lại tự động trở thành
Handlertrong Axum (gợi ý: liên quan tới trait được implement cho nhiều arity của tuple function).Đọc phần
spawn/JoinHandletrong sourcetokio, đối chiếu với kiến thứcFuture/Pinđã học ở Ngày 24–26.
Tuần 18:
Ngày 69 - Đọc source thư viện chuẩn:
Đọc source
Vec<T>,Option<T>,Result<T, E>trongstdBài tập:
Đọc phần cấp phát bộ nhớ của
Vec<T>(đối chiếu vớiNonNull/MaybeUninitđã học ở Ngày 48).Đọc các method combinator của
Option/Result(map,and_then,ok_or...), liệt kê 5 method bạn chưa từng dùng và thử áp dụng vào code cũ.Viết một tóm tắt (bằng lời của bạn, không copy) về một thiết kế trong
stdmà bạn thấy hay và giải thích vì sao.
Ngày 70 - Tổng kết:
Ôn lại toàn bộ lộ trình, xác định các mảng còn yếu để học sâu thêm
Bài tập:
Tự làm một bài test nhỏ: viết lại (không xem lại code cũ) 5 khái niệm khó nhất trong lộ trình (ownership, lifetime, Pin, variance, monomorphization) bằng lời của chính bạn.
Chọn một crate mã nguồn mở vừa và nhỏ (không phải tokio/serde/axum), đọc toàn bộ trong 1 ngày và viết lại kiến trúc của nó.
Lên kế hoạch học tiếp theo: đóng góp một PR nhỏ (dù chỉ là sửa doc) cho một crate mã nguồn mở Rust bạn hay dùng.
Bài tập bổ sung (dự án phụ, làm song song với lộ trình chính):
Xây dựng công cụ CLI kiểm tra/cập nhật dependency cho project Rust (kiểu cargo-outdated), gồm các bước:
Nghiên cứu
Cargo.toml/Cargo.lock, dùngcargo metadatađể lấy thông tin dependency hiện tại.So sánh phiên bản hiện tại với phiên bản mới nhất trên crates.io, hiển thị danh sách cần cập nhật.
Xây dựng chức năng cập nhật (
cargo update), xử lý xung đột phiên bản, backupCargo.locktrước khi cập nhật.Thêm tính năng tạo báo cáo, tối ưu hiệu suất, viết unit test/integration test.
Đóng gói bằng Cargo, viết README, và (tùy chọn) đăng lên crates.io hoặc GitHub.