Skip to main content

Command Palette

Search for a command to run...

70 ngày học Rust

Updated
33 min readView as Markdown

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:

      1. 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).

      2. 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.

      3. 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:

      1. Viết hàm tính giai thừa của một số nguyên dương.

      2. Viết chương trình kiểm tra một số có phải là số nguyên tố hay không.

      3. 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)

    • Copy trait: kiểu nào Copy, kiểu nào không, và tại sao

    • Bài tập:

      1. 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.

      2. 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ự.

      3. 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:

      1. 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.

      2. 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).

      3. Giải thích vì sao Rust không cho phép vừa giữ &mut vừ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

    • 'static lifetime: khi nào thật sự cần, khi nào là dấu hiệu thiết kế sai

    • Bài tập:

      1. Viết hàm longest<'a>(x: &'a str, y: &'a str) -> &'a str trả về chuỗi dài hơn, đảm bảo tính hợp lệ của tham chiếu.

      2. Tạo struct Important<'a> chứa một &'a str, viết method sử dụng lifetime đó.

      3. 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:

      1. Viết hàm tính tổng các phần tử trong một slice của số nguyên.

      2. Tạo một hàm tìm kiếm vị trí của một phần tử trong một slice.

      3. 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:

      1. Tạo một struct Point để biểu diễn một điểm trong mặt phẳng 2 chiều.

      2. Viết hàm tính khoảng cách giữa hai điểm.

      3. Tạo một struct Rectangle vớ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:

      1. Thêm các phương thức areaperimeter vào struct Rectangle.

      2. Tạo một struct Circle và viết phương thức tính diện tích và chu vi của hình tròn.

      3. 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ó null

    • Bài tập:

      1. Tạo một enum Coin với các biến thể Penny, Nickel, Dime, Quarter. Viết hàm chuyển đổi giá trị của Coin sang số cent tương ứng.

      2. 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.

      3. 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)

    • DerefDrop trait: cơ chế đứng sau smart pointer và RAII

    • Bài tập:

      1. Cài đặt một cons list (linked list kiểu Lisp) bằng Box.

      2. Tự implement một smart pointer tối giản (MyBox<T>) có Deref.

      3. 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) vs Arc<T> (multi-thread): reference counting, ownership chia sẻ

    • Weak<T>: phá vòng lặp tham chiếu (reference cycle), tránh memory leak

    • Bài tập:

      1. Dựng cấu trúc cây (tree) có node cha/con dùng Rc cho con và Weak cho cha.

      2. Tạo một ví dụ có reference cycle bằng Rc<RefCell<T>> không dùng Weak, quan sát memory leak, rồi sửa bằng Weak.

      3. Viết chương trình đếm số lượng owner hiện tại của một Rc bằng Rc::strong_count.

  • Ngày 12:

    • Interior Mutability: Cell<T>, RefCell<T> - mutate dữ liệu qua immutable reference, borrow checking lúc runtime

    • Pattern Rc<RefCell<T>>: shared mutable state - pattern cực kỳ phổ biến trong Rust thực tế

    • Bài tập:

      1. Cài một "shared counter" có nhiều owner (Rc) cùng sửa được giá trị (RefCell).

      2. Viết một đoạn code cố tình gây already borrowed panic lúc runtime với RefCell, giải thích tại sao đây là đánh đổi hợp lý so với static borrow checker.

      3. So sánh Cell<T>RefCell<T>: khi nào dùng cái nào (gợi ý: Cell cho kiểu Copy, RefCell cho 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 Trait vs dyn Trait): static dispatch vs dynamic dispatch, đánh đổi hiệu năng/binary size

    • Bài tập:

      1. Viết một hàm generic tính tổng các phần tử trong một slice hoặc vector.

      2. Tạo một trait Summary với phương thức summarize và implement cho các kiểu dữ liệu NewsArticleTweet.

      3. 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ào

    • Supertrait, default method, operator overloading (std::ops)

    • Bài tập:

      1. Tự định nghĩa một trait có associated type (ví dụ trait Container với type Item).

      2. Implement Add cho struct Point để hai điểm cộng được bằng toán tử +.

      3. Viết một trait Animal với default method describe(), và một supertrait yêu cầu implement std::fmt::Display.

  • Ngày 15:

    • Memory Layout & Type System: size_of::<T>(), alignment, padding

    • repr(C), repr(transparent), repr(packed) - khi nào cần kiểm soát layout thay vì để compiler tự sắp xếp

    • DST (Dynamically Sized Type) và ZST (Zero Sized Type); thin pointer vs fat pointer; vtable và layout của trait object

    • Bài tập:

      1. Dùng std::mem::size_ofstd::mem::align_of in 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.

      2. Giải thích và minh họa bằng code vì sao &str là fat pointer (ptr, len) còn &i32 là thin pointer, dùng size_of::<&str>() vs size_of::<&i32>() để chứng minh.

      3. Viết một ví dụ &dyn Trait, in size_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:

      1. Viết một struct RequestBuilder theo Builder Pattern (method chain trả về Self) để build một HTTP request giả lập.

      2. Dùng Newtype Pattern bọc String thành Email(String) với validation khi khởi tạo, giải thích lợi ích so với dùng String trần.

      3. 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ùng PhantomData để đánh dấu trạng thái.

Tuần 5:

  • Ngày 17:

    • Trait Coherence & Orphan Rule: chỉ được impl Trait for Type nế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ả Self theo giá trị...)

    • Bài tập:

      1. 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.

      2. 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.

      3. So sánh CloneDisplay/Debug trong std: giải thích vì sao Clone không object-safe (gợi ý: trả Self theo giá trị) còn hai trait kia thì có.

  • Ngày 18:

    • Variance: covariance, contravariance, invariance - vì sao &'static str coerce được thành &'a str nhưng &mut T thì invariant theo T

    • PhantomData<T>: đánh dấu ownership/variance "ảo" khi struct không thực sự chứa T (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ông

    • Bài tập:

      1. Viết ví dụ minh họa vì sao &'a T là covariant theo 'a nhưng &'a mut T là 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.

      2. Viết một struct chứa raw pointer *const T nhưng cần thêm PhantomData<T> để compiler suy luận đúng variance/ownership, giải thích nếu thiếu PhantomData thì sai ở đâu.

      3. 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ành Send bằng unsafe 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ục

    • Bài tập:

      1. Viết chương trình cố tình gây ra lỗi panic! và quan sát kết quả.

      2. Tìm hiểu về backtrace và cách nó giúp gỡ lỗi khi xảy ra panic!.

      3. 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ức unwrap, 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), From trait để auto-convert lỗi

    • Bài tập:

      1. Viết lại hàm tính căn bậc hai ở ngày 19 sử dụng Result để xử lý lỗi.

      2. Viết một module đọc file cấu hình, dùng thiserror định nghĩa ConfigError với các variant rõ ràng (NotFound, ParseError, InvalidValue), propagate lỗi bằng ?.

      3. Viết một hàm ở tầng "application" dùng anyhow::Result gọ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ùng thiserror ở ngày trước.

Tuần 6:

  • Ngày 21:

    • Threads: std::thread::spawn, JoinHandle

    • SendSync: hai trait quyết định cái gì được chia sẻ giữa thread, compiler enforce tại compile-time

    • Bài tập:

      1. 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.

      2. Viết một đoạn code cố tình vi phạm Send/Sync (ví dụ chia sẻ Rc giữa các thread) và quan sát lỗi compiler.

      3. Dùng JoinHandle để thu thập kết quả trả về từ nhiều thread vào một Vec.

  • Ngày 22:

    • Chia sẻ state giữa threads: Mutex<T>, RwLock<T>, pattern Arc<Mutex<T>>

    • Deadlock: nguyên nhân và cách tránh

    • Bài tập:

      1. Cài bộ đếm dùng chung giữa 10 thread bằng Arc<Mutex<i32>>.

      2. Tạo một deadlock có chủ đích (ví dụ hai thread khóa hai Mutex theo thứ tự ngược nhau) rồi sửa.

      3. So sánh MutexRwLock: viết ví dụ nhiều reader/một writer dùng RwLock.

  • Ngày 23:

    • Message passing: std::sync::mpsc channel

    • So sánh triết lý "share memory by communicating" (channel) vs "communicate by sharing memory" (Mutex)

    • Bài tập:

      1. Xây pipeline producer-consumer đơn giản bằng channel (1 producer, nhiều consumer).

      2. Viết chương trình dùng nhiều Sender (clone channel) gửi dữ liệu từ nhiều thread về một Receiver.

      3. 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ì sao async fn không tự chạy nếu không được await/spawn

    • So 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:

      1. 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.

      2. 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.

      3. Tìm hiểu về các executor phổ biến trong Rust (Tokio, async-std) và giải thích vì sao async fn cầ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ị move

    • Pin<T> và auto trait Unpin; ý tưởng "pin projection" (không cần thành thạo pin-project ngay, 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/!Sync

    • Bài tập:

      1. 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ỏ đó.

      2. Tự implement Future cho một struct (không dùng async fn), viết fn poll(self: Pin<&mut Self>, ...), giải thích vì sao tham số là Pin<&mut Self> chứ không phải &mut Self.

      3. Viết một type chứa RefCell<T> và giải thích tại sao nó Send nhưng không Sync; viết một type chứa Mutex<T> và giải thích tại sao nó vừa Send vừa Sync.

  • 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 Future bị drop giữa chừng

    • Bài tập:

      1. Dùng tokio::select! viết một tác vụ có timeout (race giữa tác vụ chính và một tokio::time::sleep).

      2. 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.

      3. 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:

      1. Tạo một package mới và tổ chức mã nguồn thành các module theo chức năng.

      2. Viết một module math chứa các hàm tính toán cơ bản (cộng, trừ, nhân, chia).

      3. Tạo một module utils chứ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, pub và kiểm soát khả năng truy cập

    • Cargo workspace: quản lý nhiều crate trong một project - pattern phổ biến ở production (tách core, cli, api thành các crate riêng)

    • Bài tập:

      1. 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.

      2. 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).

      3. Dựng một Cargo workspace gồm 2 crate (một lib chứa logic, một bin dùng lại logic đó) dùng chung Cargo.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:

      1. 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ử.

      2. Viết hàm tính tổng các phần tử trong một vector.

      3. 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 gian

    • Trait Hash, Eq, PartialEq, Ord, PartialOrd: vì sao dùng struct tự định nghĩa làm key của HashMap/BTreeMap cần các trait này, và derive macro #[derive(Hash, Eq, PartialEq)]

    • Bài tập:

      1. Đếm tần suất xuất hiện của từng từ trong một văn bản bằng HashMap.

      2. Dùng HashSet để loại bỏ phần tử trùng lặp trong một danh sách.

      3. Tạo một struct UserId dùng làm key của HashMap<UserId, User>: derive Hash, 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ủa BTreeMap, derive thêm Ord, PartialOrd, quan sát thứ tự duyệt khác HashMap thế 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:

      1. 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.

      2. 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.

      3. 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: Iterator trait tự implement, lazy evaluation, zero-cost abstraction

    • IntoIterator vs Iterator; sự khác biệt giữa iter(), iter_mut(), into_iter(); FromIterator và cách collect() thực sự hoạt động

    • Bài tập:

      1. Sử dụng iterator để tính tổng các phần tử trong một vector.

      2. Tạo một iterator tùy chỉnh để sinh ra các số Fibonacci (implement Iterator cho một struct).

      3. Viết một hàm nhận impl IntoIterator<Item = i32> thay vì &Vec<i32>, gọi thử với Vec, mảng, và HashSet để thấy tính tổng quát; sau đó tự implement FromIterator cho 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ệt

    • Bài tập:

      1. Viết một closure để sắp xếp một vector theo thứ tự tăng dần.

      2. Sử dụng closure để lọc các phần tử chẵn trong một vector.

      3. 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à Fn hay FnMut.

  • 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 macro

    • Giới thiệu proc macro (derive macro) ở mức đọc hiểu - vì sao #[derive(Serialize)] hoạt động được

    • Bài tập:

      1. Viết một macro_rules! đơn giản (ví dụ vec_of_strings! sinh ra Vec<String> từ danh sách literal).

      2. Viết một macro nhận số lượng tham số thay đổi (dùng $(...)*) để tính tổng nhiều số.

      3. Đọ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:

      1. Viết unit test cho hàm tính căn bậc hai đã tạo trước đó.

      2. Viết unit test cho các phương thức của struct Rectangle.

      3. 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:

      1. Viết integration test cho chương trình dòng lệnh đã tạo trước đó.

      2. Viết integration test cho chương trình đọc ghi tệp tin.

      3. 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ố định

    • Benchmark với criterion

    • Bài tập:

      1. 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).

      2. 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.

      3. 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ùng perf (Linux) ở mức cơ bản

    • Heap 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:

      1. Chạy cargo flamegraph trê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.

      2. 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.

      3. Đọc báo cáo HTML mà criterion sinh 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 doc

    • CI cơ bản: chạy cargo test, cargo clippy, cargo fmt --check trong GitHub Actions

    • Bài tập:

      1. Chạy cargo clippy trên toàn bộ code đã viết từ đầu khóa, sửa hết warning thay vì ignore.

      2. Viết doc comment (///) cho các hàm public trong một module, generate bằng cargo doc --open.

      3. Viết file .github/workflows/ci.yml chạ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:

      1. Viết chương trình đọc nội dung của một tệp tin và in ra màn hình.

      2. 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.

      3. 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::env

    • Bài tập:

      1. Sử dụng std::fs để liệt kê các tệp tin trong một thư mục.

      2. Sử dụng std::io để đọc ghi dữ liệu từ bàn phím và màn hình.

      3. 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 qua yaml, 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:

      1. Sử dụng Serde để đọc và ghi dữ liệu JSON từ/vào tệp tin.

      2. Đọc một file cấu hình dạng toml bằng serde + crate toml, so sánh trải nghiệm với đọc JSON.

      3. 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 chrono hoặc time để serialize/deserialize ngày giờ

    • Bài tập:

      1. Viết struct có field optional dùng #[serde(default)], test với JSON thiếu field đó.

      2. Viết struct có field kiểu DateTime (dùng chrono), serialize/deserialize qua JSON, xử lý đúng timezone.

      3. Xử lý lỗi parse JSON một cách graceful (không panic), trả về Result có 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:

      1. Sử dụng Regex để kiểm tra định dạng email.

      2. Sử dụng Regex để trích xuất thông tin từ một đoạn văn bản.

      3. 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:

      1. Sử dụng Clap để tạo một chương trình CLI đơn giản.

      2. Sử dụng Clap để tạo một chương trình CLI có nhiều subcommand.

      3. 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.rs

    • Bài tập:

      1. Thêm feature flag tùy chọn cho một crate (ví dụ bật/tắt một module qua [features]).

      2. Tìm hiểu và cấu hình custom profile trong Cargo.toml (ví dụ [profile.release] với lto = true).

      3. 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.lock vs Cargo.toml trong context production (khi nào commit lock file)

    • Bài tập:

      1. Tìm và sử dụng một thư viện từ crates.io để xử lý định dạng JSON.

      2. 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.

      3. Giải thích semantic versioning (^1.2.3 nghĩa là gì) và vì sao binary/service thường commit Cargo.lock cò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 trong Vec, 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:

      1. 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.

      2. Implement một Vec-like structure tối giản dùng NonNull<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ần unsafe và invariant phải giữ để an toàn.

      3. Đọc source code phần cấp phát bộ nhớ bên trong Vec<T> trong std, chỉ ra chỗ nào dùng NonNull/MaybeUninit và 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:

      1. Dùng cargo expand xem code thực tế sinh ra từ một #[derive(Debug)] và từ một macro_rules! đã viết ở Ngày 34.

      2. Viết một hàm generic đơn giản, gọi với 3 kiểu khác nhau (i32, f64, String), dùng cargo bloat hoặ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.

      3. 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:

      1. Sử dụng FFI để gọi một hàm C đơn giản.

      2. 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).

      3. 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:

      1. Sử dụng xsv để lọc, chuyển đổi, và phân tích dữ liệu CSV.

      2. Sử dụng ripgrep để tìm kiếm các đoạn mã trong dự án của bạn.

      3. Sử dụng fdbat để 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:

      1. Viết một REST API CRUD đơn giản (in-memory store) cho một resource, ví dụ "todo".

      2. Thêm route với Path param (ví dụ GET /todos/:id) và Query param (ví dụ filter theo trạng thái).

      3. Dùng State để chia sẻ một Arc<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 qua IntoResponse

    • Bài tập:

      1. Thêm middleware log mỗi request (method, path, thời gian xử lý) dùng tower::Layer.

      2. Định nghĩa error type riêng cho API, implement IntoResponse để trả JSON error nhất quán (status code + message).

      3. Thêm middleware CORS cho API.

  • Ngày 54:

    • Database với sqlx: compile-time checked query, connection pool, migration

    • Dùng uuid để sinh khóa chính; giới thiệu DashMap như 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:

      1. 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.

      2. Thay in-memory store bằng CRUD thật qua sqlx (tạo, đọc, sửa, xóa).

      3. Thêm một lớp cache đơn giản bằng DashMap trước khi query database, so sánh với cách dùng Arc<Mutex<HashMap>>.

  • Ngày 55:

    • Testing cho web service: test handler với tower::ServiceExt::oneshot, test database bằng transaction rollback pattern

    • Bài tập:

      1. Viết integration test cho flow tạo → đọc → sửa → xoá resource qua HTTP.

      2. 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.

      3. 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, config crate), phân biệt config dev/staging/production

    • reqwest: 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:

      1. Tách connection string database, port, log level... ra file .env và struct Config đọc từ env.

      2. Viết validation cho Config khi khởi động (fail-fast nếu thiếu biến bắt buộc).

      3. Dùng reqwest gọ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: tracing crate - span, event, khác biệt với log crate truyền thống

    • Bài tập:

      1. Thay toàn bộ println! trong web service (Tuần 13–14) bằng tracing.

      2. Thêm structured field vào log (ví dụ request_id, user_id) để dễ tìm kiếm sau này.

      3. 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:

      1. Expose endpoint /metrics cho web service, đếm tổng số request theo route.

      2. Đo và expose latency (p50/p95) của mỗi endpoint.

      3. Thêm endpoint /healthz trả 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 size

    • Bài tập:

      1. So sánh thời gian chạy và kích thước binary giữa build debugrelease.

      2. Bật lto = true và giảm codegen-units, đo lại thời gian build và hiệu năng.

      3. 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:

      1. Viết Dockerfile multi-stage cho web service đã xây.

      2. So sánh kích thước image giữa bản build đơn giản và bản multi-stage tối ưu.

      3. 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, target musl cho container tối giản, pipeline test → build → docker build → push

    • Bài tập:

      1. Cross-compile ứng dụng sang target x86_64-unknown-linux-musl.

      2. Viết pipeline CI/CD hoàn chỉnh: chạy test → build release → build docker image → (giả lập) push image.

      3. 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:

      1. Vẽ sơ đồ kiến trúc tổng thể (API layer, service layer, database, cache).

      2. 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.

      3. 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:

      1. Implement endpoint tạo short URL (sinh mã ngẫu nhiên hoặc hash, lưu vào Postgres).

      2. Implement endpoint redirect (GET /:code) và tăng bộ đếm click.

      3. Thêm cache (DashMap hoặ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:

      1. Viết integration test cho flow tạo → redirect → xem thống kê.

      2. Thêm tracing structured log và endpoint /metrics.

      3. 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:

      1. Viết Dockerfile multi-stage và docker-compose.yml (app + Postgres + Redis nếu dùng).

      2. Chạy docker compose up và kiểm tra service hoạt động end-to-end.

      3. 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:

      1. Viết README.md mô tả kiến trúc, cách chạy local, cách chạy test.

      2. 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.

      3. (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ưởng Visitor pattern trong deserialize

    • Bài tập:

      1. Đọc phần derive của serde, đối chiếu với những gì đã học ở Ngày 34/49 về macro và monomorphization.

      2. Đọc ý tưởng Visitor trong deserialize của serde, 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.

      3. Viết một Deserialize implementation 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ách RouterHandler được implement dựa trên trait tower::Service

    • Bài tập:

      1. Truy vết một request đi qua axum từ lúc router nhận vào tới lúc gọi handler, đối chiếu với kiến thức tower::Layer/Service đã học ở Ngày 53.

      2. 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 Handler trong Axum (gợi ý: liên quan tới trait được implement cho nhiều arity của tuple function).

      3. Đọc phần spawn/JoinHandle trong source tokio, đối chiếu với kiến thức Future/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> trong std

    • Bài tập:

      1. Đọc phần cấp phát bộ nhớ của Vec<T> (đối chiếu với NonNull/MaybeUninit đã học ở Ngày 48).

      2. Đọ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ũ.

      3. 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 std mà 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:

      1. 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.

      2. 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ó.

      3. 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:

  1. Nghiên cứu Cargo.toml/Cargo.lock, dùng cargo metadata để lấy thông tin dependency hiện tại.

  2. 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.

  3. Xây dựng chức năng cập nhật (cargo update), xử lý xung đột phiên bản, backup Cargo.lock trước khi cập nhật.

  4. 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.

  5. Đóng gói bằng Cargo, viết README, và (tùy chọn) đăng lên crates.io hoặc GitHub.

Knowledge

Part 1 of 50