Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust Tutorial Hero

บทนำ - ยินดีต้อนรับสู่ Rust! 🦀

Rust คืออะไร?

Rust เป็นภาษาโปรแกรมระดับ Systems Programming ที่มุ่งเน้น 3 เรื่องหลัก: ความปลอดภัย (Safety), ความเร็ว (Speed), และ ความสามารถในการทำงานพร้อมกัน (Concurrency) โดยไม่ต้องมี Garbage Collector

🧠 ปรัชญาของ Rust (The Three Pillars)

Rust ถูกออกแบบมาเพื่อแก้ปัญหา Trade-off เดิมๆ ที่ว่า “ถ้าอยากได้ความปลอดภัย ต้องยอมแลกด้วยความเร็ว” ด้วย 3 เสาหลัก:

1. Memory Safety โดยไม่มี Garbage Collector

Rust ตรวจสอบความถูกต้องของหน่วยความจำตั้งแต่ตอน Compile (ผ่านระบบ Ownership) ไม่ต้องมี GC มาถ่วงตอน Runtime

แนวทางภาษาข้อดี/ข้อเสีย
Manual MemoryC/C++เร็วมาก แต่เสี่ยง Buffer Overflows / Memory Leaks
Garbage CollectionJava, Go, Pythonปลอดภัย แต่กินแรมและมีช่วงหยุด (GC Pause)
Ownership SystemRustปลอดภัย + เร็ว (Zero Runtime Overhead) ✅

2. Zero-Cost Abstractions

คุณสามารถเขียนโค้ด High-level (เช่น Iterators, Closures, Generics) ที่อ่านง่าย แต่เมื่อ Compile แล้วจะได้ Machine Code ที่เร็วเท่ากับการเขียนแบบ Low-level ด้วยมือ

📌 กฎเหล็ก: “สิ่งที่คุณไม่ได้ใช้ คุณไม่ต้องจ่าย และสิ่งที่คุณใช้ คุณไม่สามารถเขียนเองด้วยมือให้ดีกว่าที่ภาษาทำให้ได้”

3. Fearless Concurrency

Rust Compiler ช่วยป้องกัน Data Races ตั้งแต่ตอนเขียนโค้ด ทำให้นักพัฒนา “กล้า” เขียนโปรแกรมที่ทำงานขนานกัน (Multi-threading) ได้อย่างเต็มประสิทธิภาพโดยไม่ต้องกลัวบั๊กที่หาเจอยาก


🆚 เปรียบเทียบกับภาษาอื่น

ด้านC/C++GoPythonRust
ความเร็ว⚡⚡⚡⚡⚡⚡⚡⚡
Memory Safety❌ Manual✅ GC✅ GC✅ Ownership
Concurrency⚠️ ยาก✅ Goroutines⚠️ GIL✅ Fearless
Learning Curve⚠️ สูง✅ ง่าย✅ ง่าย⚠️ สูง
GC Pauseไม่มีมีมีไม่มี

💡 สรุป: Rust ให้ความเร็วเท่า C/C++ แต่ปลอดภัยเท่า Go/Java โดยไม่ต้องมี Garbage Collector!


ทำไมต้องเรียน Rust?

นอกเหนือจากความเทพทางเทคนิคแล้ว นี่คือเหตุผลที่คุณควรลงทุนเวลากับภาษานี้:

เหตุผลคำอธิบาย
📦 Modern ToolingCargo คือ Package Manager ที่ดีที่สุดตัวหนึ่งของโลก จัดการ dependencies, build, test, docs ได้ในตัวเดียว
💼 อุตสาหกรรมต้องการบริษัทระดับโลก (Microsoft, Google, AWS, Meta) ใช้ Rust ใน Core Infrastructure และต้องการคนเขียนเป็น
🏆 Most Loved Languageครองแชมป์ใน Stack Overflow Survey ติดต่อกันหลายปี นักพัฒนาที่ได้ลองใช้มักจะติดใจ
Strict CompilerCompiler ที่จู้จี้แต่ใจดี เหมือนมี Senior Dev คอยรีวิวและสอนโค้ดให้คุณตลอดเวลา

Rust เหมาะกับงานอะไร?

ประเภทงานตัวอย่าง
🖥️ Systems ProgrammingOS, Drivers, Compilers
🌐 Web ServicesBackend APIs, Microservices
⚡ WebAssemblyHigh-performance Web Apps
🛠️ CLI ToolsCommand-line Applications
📱 EmbeddedIoT, Robotics
🎮 Game EnginesGame Development
🔐 BlockchainCrypto, Smart Contracts

🌍 ใครใช้ Rust?

บริษัทชั้นนำระดับโลกเปลี่ยนมาใช้ Rust เพราะความปลอดภัยและประสิทธิภาพ:

บริษัทโปรเจกต์ทำไมถึงเลือก Rust
MicrosoftWindows kernelMemory safety ใน OS
GoogleAndroid (Binder)ลดช่องโหว่ด้านความปลอดภัย
MetaMononokeConcurrency ที่ดีกว่า
AWSFirecrackerLow latency สำหรับ Lambda
CloudflarePingoraแทน nginx, เร็วกว่า 3 เท่า
DiscordRead StatesGo → Rust, ลด latency spike
LinuxRust for Linuxภาษาที่ 2 หลัง C ใน kernel

💡 รู้หรือไม่? Discord เคยมีปัญหา latency spike ทุก 2 นาที เมื่อเปลี่ยนจาก Go เป็น Rust ปัญหาหายไปเลย!


📜 ประวัติความเป็นมา

Graydon Hoare พนักงานของ Mozilla เริ่มพัฒนา Rust เป็นโปรเจกต์ส่วนตัวในปี 2006 (ตั้งชื่อตามเชื้อราสนิมที่มีความทนทาน) ก่อนที่ Mozilla จะเห็นศักยภาพและสนับสนุนอย่างเป็นทางการในปี 2009

🗓️ Rust Timeline

ปีเหตุการณ์สำคัญ
2006เริ่มพัฒนาเป็น Side Project
2009Mozilla ประกาศสนับสนุนอย่างเป็นทางการ
2010เปิดตัวต่อสาธารณะครั้งแรก
2015🎉 Rust 1.0 เปิดตัว (Stable Release) พร้อมรับประกัน Backward Compatibility
2018Rust 2018 Edition (ปรับปรุงครั้งใหญ่, เพิ่ม async/await ในเวลาต่อมา)
2021ก่อตั้ง Rust Foundation (AWS, Google, Microsoft, Huawei, Mozilla)
2024🐧 Rust เข้าสู่ Linux Kernel อย่างเป็นทางการ (ภาษาที่ 2 ถัดจาก C)

หนังสือเล่มนี้มีอะไรบ้าง?

📊 ภาพรวม

เนื้อหาจำนวน
📖 บทเรียน20 บท
✍️ แบบฝึกหัด100+ ข้อ
❓ Quiz100+ คำถาม
📋 Cheatsheetครบทุกหัวข้อ
🔗 Resourcesแหล่งเรียนรู้เพิ่มเติม

🎯 Edition

หนังสือเล่มนี้ใช้ Rust Edition 2024 ซึ่งเป็นเวอร์ชันล่าสุด


หนังสือเล่มนี้สำหรับใคร?

หนังสือเล่มนี้เขียนขึ้นสำหรับ ผู้เริ่มต้น ที่:

  • 🆕 ไม่เคยเขียนโปรแกรมมาก่อน - เราจะอธิบายทุกอย่างตั้งแต่พื้นฐาน
  • 🔄 มีประสบการณ์ภาษาอื่น - และอยากเรียน Rust

โครงสร้างหนังสือ

หนังสือแบ่งออกเป็น 5 ส่วน:

Part 1: พื้นฐาน (บทที่ 1-4)

เริ่มต้นจากการติดตั้ง เขียน Hello World ไปจนถึงพื้นฐานการเขียนโปรแกรม

Part 2: Core Concepts (บทที่ 5-9)

หัวใจสำคัญของ Rust - Ownership ⭐, Structs, Enums, Error Handling

Part 3: Advanced Concepts (บทที่ 10-14)

Generics, Traits, Lifetimes, Modules, Testing, Iterators, Smart Pointers

Part 4: Concurrency & Advanced (บทที่ 15-18)

การเขียนโปรแกรมแบบ concurrent, async/await, unsafe, macros

Part 5: Real World (บทที่ 19-20)

Web Development และ Final Project - สร้างโปรเจกต์จริง


วิธีใช้หนังสือเล่มนี้

💡 คำแนะนำ

  1. 📚 อ่านตามลำดับ - แต่ละบทต่อยอดจากบทก่อนหน้า
  2. ⌨️ ลงมือทำ - พิมพ์โค้ดเอง อย่า copy-paste
  3. 🔬 ทดลอง - แก้โค้ดดู ลองผิดลองถูก
  4. ✍️ ทำแบบฝึกหัด - ท้ายบทจะมีแบบฝึกหัดให้ทำ
  5. ❓ ทำ Quiz - ทดสอบความเข้าใจ

📝 สัญลักษณ์ที่ใช้

ตลอดทั้งเล่ม คุณจะเห็นกล่องข้อความเหล่านี้:

📌 หมายเหตุ: ข้อมูลเพิ่มเติมที่น่าสนใจ

💡 เคล็ดลับ: คำแนะนำที่จะทำให้เขียนโค้ดได้ดีขึ้น

⚠️ คำเตือน: สิ่งที่ควรระวัง

🎯 ลองทำดู: แบบฝึกหัดให้ลองทำ


เครื่องมือที่ต้องการ

ก่อนเริ่มเรียน ติดตั้งสิ่งเหล่านี้:

เครื่องมือคำอธิบาย
Rustภาษา Rust + Cargo
VS CodeText Editor แนะนำ
rust-analyzerExtension สำหรับ VS Code

พร้อมแล้วหรือยัง?

มาเริ่มการเดินทางกับ Rust กันเลย! 🚀

👉 ไปบทที่ 1: Getting Started - เริ่มต้นกับ Rust

☕ สนับสนุนผู้เขียน

ถ้าหนังสือเล่มนี้มีประโยชน์และช่วยให้คุณเข้าใจ Rust มากขึ้น คุณสามารถเลี้ยงกาแฟผมได้ที่: Buy Me a Coffee ☕


🦀 Happy Coding with Rust!

บทที่ 1: Getting Started - เริ่มต้นกับ Rust 🚀

ยินดีต้อนรับสู่การเขียนโปรแกรมด้วย Rust! ในบทนี้เราจะ:

  • ✅ ติดตั้ง Rust บนคอมพิวเตอร์ของคุณ
  • ✅ เขียนโปรแกรม “Hello, World!”
  • ✅ เรียนรู้พื้นฐานการใช้ Cargo

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
การติดตั้งติดตั้ง Rust ด้วย rustup
Hello Worldเขียนโปรแกรมแรกของคุณ
Cargoเครื่องมือจัดการโปรเจกต์

เริ่มกันเลย!

👉 การติดตั้ง Rust

การติดตั้ง Rust

ติดตั้งด้วย rustup

rustup เป็นเครื่องมืออย่างเป็นทางการสำหรับติดตั้งและจัดการ Rust

🛠️ Rust Toolchain Overview

+-------------------------------------------------------------------+
|                  Rust Development Toolchain                       |
+-------------------------------------------------------------------+
|                                                                   |
|  rustup (Toolchain Manager)                                       |
|  +-- rustc (Compiler)      <-- compile .rs to executable          |
|  +-- cargo (Build Tool)    <-- manage project + dependencies      |
|  +-- rustfmt (Formatter)   <-- auto format code                   |
|  +-- clippy (Linter)       <-- best practices suggestions         |
|  +-- rust-docs (Docs)      <-- offline documentation              |
|                                                                   |
|  IDE Setup (Recommended)                                          |
|  +-------------------------------------------------------+        |
|  |  VS Code + rust-analyzer extension                    |        |
|  |  * Auto-complete                                      |        |
|  |  * Error highlighting                                 |        |
|  |  * Go to definition                                   |        |
|  |  * Inline type hints                                  |        |
|  +-------------------------------------------------------+        |
|                                                                   |
+-------------------------------------------------------------------+

สำหรับ Windows

  1. ดาวน์โหลด rustup จาก https://rustup.rs
  2. เปิดไฟล์ที่ดาวน์โหลดมาและทำตามขั้นตอน
  3. อาจต้องติดตั้ง Visual Studio Build Tools ด้วย

สำหรับ macOS และ Linux

เปิด Terminal แล้วพิมพ์คำสั่ง:

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

ทำตามขั้นตอนที่แสดงบนหน้าจอ


ตรวจสอบการติดตั้ง

หลังติดตั้งแล้ว ปิดและเปิด Terminal ใหม่ แล้วพิมพ์:

rustc --version

ถ้าติดตั้งสำเร็จ จะเห็นข้อความคล้ายๆ นี้:

rustc 1.83.0 (90b35a623 2024-11-26)

ลองตรวจสอบ Cargo ด้วย:

cargo --version

จะได้:

cargo 1.83.0 (5ffbef321 2024-10-29)

อัปเดต Rust

Rust ออกเวอร์ชันใหม่ทุก 6 สัปดาห์ อัปเดตได้ด้วย:

rustup update

เลือก IDE / Text Editor

แนะนำให้ใช้ VS Code พร้อม extension:

  1. rust-analyzer - สำคัญมาก!
  2. Even Better TOML
  3. Error Lens

หรือจะใช้ RustRover ซึ่งเป็น IDE เฉพาะสำหรับ Rust จาก JetBrains ก็ได้


สรุป

✅ ติดตั้ง Rust ด้วย rustup
✅ ตรวจสอบด้วย rustc --version
✅ ติดตั้ง VS Code + rust-analyzer

👉 ต่อไป: Hello World

Hello, World!

มาเขียนโปรแกรมแรกกัน! ตามธรรมเนียมของการเรียนภาษาใหม่ เราจะเริ่มด้วย “Hello, World!”

สร้างไฟล์โปรแกรม

  1. สร้างโฟลเดอร์สำหรับโปรเจกต์:
mkdir hello_world
cd hello_world
  1. สร้างไฟล์ main.rs:
fn main() {
    println!("Hello, World!");
}

รันโปรแกรม

Compile และรัน:

rustc main.rs
./main        # บน macOS/Linux
.\main.exe    # บน Windows

ผลลัพธ์:

Hello, World!

🎉 ยินดีด้วย! คุณเพิ่งเขียนโปรแกรม Rust โปรแกรมแรก!


ทำความเข้าใจโค้ด

มาวิเคราะห์โค้ดทีละบรรทัด:

fn main() {
    println!("Hello, World!");
}

fn main()

fn main() {
  • fn - คำสั่งสำหรับประกาศ function (ฟังก์ชัน)
  • main - ชื่อฟังก์ชัน พิเศษเพราะเป็นจุดเริ่มต้นของโปรแกรม
  • () - ไม่มี parameter (ค่าที่ส่งเข้ามา)
  • { } - ขอบเขตของฟังก์ชัน (function body)

หมายเหตุ: ทุกโปรแกรม Rust ต้องมีฟังก์ชัน main เป็นจุดเริ่มต้น

println!("Hello, World!");

println!("Hello, World!");
  • println! - เป็น macro (สังเกตเครื่องหมาย !) ใช้พิมพ์ข้อความออกหน้าจอ
  • "Hello, World!" - string (ข้อความ) ที่จะพิมพ์
  • ; - จบคำสั่ง (statement)

เคล็ดลับ: ใน Rust เครื่องหมาย ! หลังชื่อหมายถึง macro ไม่ใช่ function เราจะเรียนเรื่อง macro ในบทหลังๆ


รูปแบบการเขียน (Style)

Rust มีมาตรฐานการเขียนโค้ด:

  1. Indentation - ใช้ 4 spaces (ไม่ใช่ tab)
  2. ชื่อฟังก์ชัน - ใช้ snake_case เช่น my_function
  3. วงเล็บปีกกา - เปิดในบรรทัดเดียวกับ function
// ✅ ถูกต้อง
fn main() {
    println!("Hello!");
}

// ❌ ไม่ใช่ style มาตรฐาน
fn main2()
{
    println!("Hello!");
}

ใช้คำสั่ง rustfmt เพื่อจัดรูปแบบโค้ดอัตโนมัติ:

rustfmt main.rs

ลองทำดู! 🎯

  1. แก้โค้ดให้พิมพ์ชื่อของคุณ
  2. ลองเพิ่ม println! อีกบรรทัด
  3. ลองลบ ; ดูว่าเกิดอะไรขึ้น

สรุป

สิ่งที่เรียนรู้คำอธิบาย
fn main()จุดเริ่มต้นโปรแกรม
println!()พิมพ์ข้อความ
;จบคำสั่ง
rustcCompile โปรแกรม

👉 ต่อไป: Cargo เบื้องต้น

Cargo เบื้องต้น

Cargo เป็นเครื่องมือจัดการโปรเจกต์และ package manager ของ Rust มาพร้อมกับ rustup

Cargo ทำอะไรได้บ้าง?

  • 📦 สร้างโปรเจกต์ใหม่
  • 🔨 Build โค้ด
  • ▶️ รันโปรแกรม
  • 📥 จัดการ dependencies (libraries ที่ใช้)
  • 🧪 รัน tests
  • 📖 สร้าง documentation

สร้างโปรเจกต์ใหม่

cargo new hello_cargo
cd hello_cargo

Cargo จะสร้างโครงสร้างโฟลเดอร์ให้:

hello_cargo/
+-- Cargo.toml
\-- src/
    \-- main.rs

ทำความเข้าใจโครงสร้าง

Cargo.toml

[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2024"

[dependencies]
  • [package] - ข้อมูลโปรเจกต์
    • name - ชื่อโปรเจกต์
    • version - เวอร์ชัน
    • edition - Rust edition (ปกติใช้ปีล่าสุด)
  • [dependencies] - รายการ libraries ที่ใช้

src/main.rs

fn main() {
    println!("Hello, world!");
}

โค้ดเริ่มต้นที่ Cargo สร้างให้


คำสั่ง Cargo ที่ใช้บ่อย

Build โปรเจกต์

cargo build

จะสร้างไฟล์ executable ใน target/debug/:

target/debug/hello_cargo

Build แบบ Release (เร็วกว่า)

cargo build --release

ไฟล์จะอยู่ใน target/release/ - เหมาะสำหรับ production

รันโปรแกรม

cargo run

คำสั่งนี้จะ build และ run ในคำสั่งเดียว สะดวกมาก!

   Compiling hello_cargo v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
     Running target/debug/hello_cargo
Hello, world!

ตรวจสอบโค้ด (ไม่ build)

cargo check

เร็วกว่า cargo build เพราะไม่สร้างไฟล์ executable ใช้ตรวจสอบว่าโค้ด compile ได้หรือไม่


เพิ่ม Dependencies

เมื่อต้องการใช้ library ภายนอก ให้เพิ่มใน Cargo.toml:

[dependencies]
rand = "0.8"

จากนั้นรัน:

cargo build

Cargo จะดาวน์โหลด library ให้อัตโนมัติ

หมายเหตุ: Library ของ Rust เรียกว่า crate หา crates ได้ที่ crates.io


สรุปคำสั่ง Cargo

คำสั่งหน้าที่
cargo new <name>สร้างโปรเจกต์ใหม่
cargo buildCompile โปรเจกต์
cargo runCompile และ run
cargo checkตรวจสอบโค้ด
cargo build --releaseBuild สำหรับ production

ลองทำดู! 🎯

  1. สร้างโปรเจกต์ใหม่ชื่อ my_project
  2. แก้ไข main.rs ให้พิมพ์ข้อความอื่น
  3. รันด้วย cargo run
  4. ลอง cargo check ดู

สรุปบทที่ 1

ในบทนี้คุณได้เรียนรู้:

  • ✅ ติดตั้ง Rust ด้วย rustup
  • ✅ เขียนโปรแกรม Hello World
  • ✅ ใช้งาน Cargo เบื้องต้น

👉 ต่อไป: บทที่ 2: Variables & Data Types

บทที่ 2: Variables & Data Types - ตัวแปรและชนิดข้อมูล

ในบทนี้เราจะเรียนรู้พื้นฐานที่สำคัญของทุกภาษาโปรแกรม นั่นคือ ตัวแปร และ ชนิดข้อมูล


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Mutabilityตัวแปรแบบเปลี่ยนค่าได้และเปลี่ยนไม่ได้
Data Typesชนิดข้อมูลต่างๆ ใน Rust
Constantsค่าคงที่และ Shadowing

ทำไมต้องเข้าใจ?

ใน Rust ตัวแปรมีความพิเศษ:

  1. Immutable by default - ตัวแปรเปลี่ยนค่าไม่ได้โดยปกติ
  2. Type inference - Rust เดาชนิดข้อมูลให้ได้
  3. Strongly typed - ต้องระบุชนิดข้อมูลให้ชัดเจน

เริ่มกันเลย!

👉 Mutability

Mutability - ความสามารถในการเปลี่ยนแปลงค่า

ประกาศตัวแปรด้วย let

ใน Rust เราใช้ let เพื่อประกาศตัวแปร:

fn main() {
    let x = 5;
    println!("x = {}", x);
}

ผลลัพธ์:

x = 5

ตัวแปรเปลี่ยนค่าไม่ได้ (Immutable)

ตัวแปรใน Rust เปลี่ยนค่าไม่ได้โดยปกติ!

fn main() {
    let x = 5;
    println!("x = {}", x);
    x = 6; // ❌ Error!
}

Error:

error[E0384]: cannot assign twice to immutable variable `x`

ทำไมถึงเป็นแบบนี้?

เพราะ immutable ทำให้โค้ดปลอดภัยและคาดเดาได้ง่ายกว่า เมื่อค่าไม่เปลี่ยน เราไม่ต้องกังวลว่าจะมีโค้ดส่วนอื่นแก้ไขค่าโดยไม่รู้ตัว


ตัวแปรเปลี่ยนค่าได้ (Mutable)

ถ้าต้องการเปลี่ยนค่า ให้เพิ่ม mut:

fn main() {
    let mut x = 5;
    println!("x = {}", x);

    x = 6; // ✅ OK เพราะใช้ mut
    println!("x = {}", x);
}

ผลลัพธ์:

x = 5
x = 6

เมื่อไหร่ควรใช้ mut?

✅ ใช้ mut เมื่อ:

  • ต้องการสะสมค่า (เช่น ตัวนับ, ผลรวม)
  • ต้องการแก้ไขข้อมูลใน loop
fn main() {
    let mut sum = 0;

    sum = sum + 1;
    sum = sum + 2;
    sum = sum + 3;

    println!("sum = {}", sum); // 6
}

❌ ไม่ต้องใช้ mut เมื่อ:

  • ค่าไม่เปลี่ยนแปลง
  • ต้องการความปลอดภัย
fn main() {
    let name = "Rust";
    let pi = 3.14159;

    println!("Learning {} with pi = {}", name, pi);
}

Shadowing (การซ่อนตัวแปร)

Rust อนุญาตให้ประกาศตัวแปรชื่อเดิมซ้ำได้:

fn main() {
    let x = 5;
    println!("x = {}", x); // 5

    let x = x + 1; // สร้างตัวแปรใหม่ชื่อ x
    println!("x = {}", x); // 6

    let x = x * 2;
    println!("x = {}", x); // 12
}

Shadowing vs mut

Shadowingmut
สร้างตัวแปรใหม่แก้ไขตัวแปรเดิม
เปลี่ยนชนิดได้เปลี่ยนชนิดไม่ได้
ใช้ let ซ้ำไม่ต้องใช้ let
fn main() {
    // Shadowing - เปลี่ยนชนิดได้
    let spaces = "   ";      // &str (string)
    let spaces = spaces.len(); // usize (number)
    println!("spaces = {}", spaces); // 3

    // mut - เปลี่ยนชนิดไม่ได้
    // let mut spaces = "   ";
    // spaces = spaces.len(); // ❌ Error! ชนิดต่างกัน
}

ลองทำดู! 🎯

  1. ประกาศตัวแปร age และพยายามเปลี่ยนค่า ดูว่า error อะไร
  2. เพิ่ม mut แล้วลองอีกครั้ง
  3. ลอง shadowing: ประกาศ let x = 5; แล้วตามด้วย let x = "hello";

สรุป

แนวคิดคำอธิบาย
let x = 5;ตัวแปร immutable
let mut x = 5;ตัวแปร mutable
Shadowingประกาศตัวแปรชื่อเดิมซ้ำ

👉 ต่อไป: ชนิดข้อมูล

ชนิดข้อมูล (Data Types)

Rust เป็นภาษา statically typed หมายความว่าต้องรู้ชนิดของตัวแปรทั้งหมดตอน compile

ประเภทของข้อมูล

Rust แบ่งชนิดข้อมูลเป็น 2 กลุ่มหลัก:

  1. Scalar Types - ค่าเดี่ยว
  2. Compound Types - ค่าหลายค่ารวมกัน

Scalar Types

1. Integer (จำนวนเต็ม)

ขนาดSigned (มีลบ)Unsigned (บวกเท่านั้น)
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
fn main() {
    let a: i32 = 42;        // จำนวนเต็ม 32-bit
    let b: u8 = 255;        // จำนวนเต็มบวก 8-bit (0-255)
    let c = 1000;           // i32 (default)
    let d: isize = -500;    // ขึ้นกับ CPU (32 หรือ 64 bit)

    println!("a={}, b={}, c={}, d={}", a, b, c, d);
}

รูปแบบการเขียนตัวเลข

fn main() {
    let decimal = 98_222;      // Decimal (ใส่ _ ให้อ่านง่าย)
    let hex = 0xff;            // Hexadecimal
    let octal = 0o77;          // Octal
    let binary = 0b1111_0000;  // Binary
    let byte = b'A';           // Byte (u8 only)

    println!("{}, {}, {}, {}, {}", decimal, hex, octal, binary, byte);
    // 98222, 255, 63, 240, 65
}

2. Floating-Point (ทศนิยม)

fn main() {
    let x = 2.0;      // f64 (default, แม่นยำกว่า)
    let y: f32 = 3.0; // f32 (ใช้หน่วยความจำน้อยกว่า)

    println!("x={}, y={}", x, y);
}

การคำนวณ

fn main() {
    let sum = 5 + 10;           // บวก
    let difference = 95.5 - 4.3; // ลบ
    let product = 4 * 30;        // คูณ
    let quotient = 56.7 / 32.2;  // หาร
    let remainder = 43 % 5;      // เศษ (modulo)

    println!("sum={}", sum);               // 15
    println!("difference={}", difference); // 91.2
    println!("product={}", product);       // 120
    println!("quotient={}", quotient);     // 1.76...
    println!("remainder={}", remainder);   // 3
}

3. Boolean (ค่าความจริง)

fn main() {
    let t = true;
    let f: bool = false;

    println!("t={}, f={}", t, f);

    // ใช้ในเงื่อนไข
    if t {
        println!("This is true!");
    }
}

4. Character (ตัวอักษร)

fn main() {
    let c = 'z';
    let z: char = 'ℤ';
    let heart_eyed_cat = '😻';
    let thai = 'ก';

    println!("{}, {}, {}, {}", c, z, heart_eyed_cat, thai);
}

หมายเหตุ: char ใน Rust ใช้ 4 bytes และรองรับ Unicode ทั้งหมด ใช้ single quote 'a' (ไม่ใช่ double quote "a")


Compound Types

1. Tuple (ทูเพิล)

Tuple รวมค่าหลายชนิดไว้ด้วยกัน มีขนาดคงที่

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // Destructuring - แยกค่าออกมา
    let (x, y, z) = tup;
    println!("x={}, y={}, z={}", x, y, z);

    // เข้าถึงด้วย index
    let five_hundred = tup.0;
    let six_point_four = tup.1;
    let one = tup.2;

    println!("{}, {}, {}", five_hundred, six_point_four, one);
}

Unit Type

Tuple ว่าง () เรียกว่า unit ใช้แทน “ไม่มีค่า”

fn main() {
    let unit: () = ();
    println!("unit = {:?}", unit); // ()
}

2. Array (อาร์เรย์)

Array รวมค่าชนิดเดียวกันไว้ด้วยกัน มีขนาดคงที่

fn main() {
    // ประกาศ array
    let a = [1, 2, 3, 4, 5];

    // ระบุชนิดและขนาด
    let b: [i32; 5] = [1, 2, 3, 4, 5];

    // สร้าง array ที่มีค่าเหมือนกันทั้งหมด
    let c = [3; 5]; // [3, 3, 3, 3, 3]

    println!("a = {:?}", a);
    println!("b = {:?}", b);
    println!("c = {:?}", c);
}

การเข้าถึง Elements

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];  // 1
    let second = a[1]; // 2

    println!("first={}, second={}", first, second);
}

คำเตือน: ถ้าเข้าถึง index ที่ไม่มีอยู่ โปรแกรมจะ panic!

let a = [1, 2, 3];
let x = a[10]; // ❌ panic!

Type Annotation vs Type Inference

Rust สามารถเดาชนิดข้อมูลได้:

fn main() {
    // Type Inference - Rust เดาให้
    let x = 5;       // i32
    let y = 2.0;     // f64
    let z = true;    // bool

    // Type Annotation - ระบุเอง
    let a: i64 = 5;
    let b: f32 = 2.0;
    let c: bool = true;
}

ลองทำดู! 🎯

  1. ประกาศตัวแปรแต่ละชนิดและ print ออกมา
  2. สร้าง tuple ที่มี (ชื่อ, อายุ, ส่วนสูง) และแยกค่าออกมา
  3. สร้าง array ของวันในสัปดาห์

สรุป

ประเภทตัวอย่างคำอธิบาย
Integeri32, u64จำนวนเต็ม
Floatf32, f64ทศนิยม
Booleanbooltrue/false
Charactercharตัวอักษร Unicode
Tuple(i32, f64)ค่าหลายชนิด
Array[i32; 5]ค่าชนิดเดียว ขนาดคงที่

👉 ต่อไป: Constants & Shadowing

Constants & Shadowing

Constants (ค่าคงที่)

Constants คือค่าที่ไม่มีวันเปลี่ยนแปลง ใช้ const แทน let

const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265359;
const APP_NAME: &str = "My Rust App";

fn main() {
    println!("Max points: {}", MAX_POINTS);
    println!("Pi: {}", PI);
    println!("App: {}", APP_NAME);
}

กฎของ Constants

  1. ต้องระบุชนิด - ไม่สามารถให้ Rust เดาได้
  2. ต้องเป็นค่าคงที่ - ไม่ได้มาจากการคำนวณตอน runtime
  3. ใช้ได้ทุกที่ - รวมถึง global scope
  4. SCREAMING_SNAKE_CASE - ใช้ตัวพิมพ์ใหญ่และ underscore
#![allow(unused)]
fn main() {
// ✅ ถูกต้อง
const MAX_SIZE: usize = 100;

// ❌ ไม่ได้ - ต้องระบุชนิด
// const MIN_SIZE = 10;

// ❌ ไม่ได้ - ค่าต้องคงที่
// const RANDOM: u32 = rand::random();
}

Static Variables

Static คล้าย constant แต่มี memory address คงที่

static LANGUAGE: &str = "Rust";
static mut COUNTER: u32 = 0; // ⚠️ ต้องใช้ unsafe

fn main() {
    println!("Language: {}", LANGUAGE);
}

คำเตือน: static mut ต้องใช้ใน unsafe block และควรหลีกเลี่ยงถ้าเป็นไปได้


const vs static vs let

คุณสมบัติconststaticlet
เปลี่ยนค่าได้❌ (ยกเว้น mut)✅ (ถ้า mut)
ต้องระบุชนิด
Global scope
มี memory address
Inline ได้

Shadowing (ทบทวน)

Shadowing คือการประกาศตัวแปรชื่อเดิมซ้ำ:

fn main() {
    let x = 5;

    // Shadow ด้วยค่าใหม่
    let x = x + 1;

    {
        // Shadow ใน inner scope
        let x = x * 2;
        println!("Inner x: {}", x); // 12
    }

    println!("Outer x: {}", x); // 6
}

ประโยชน์ของ Shadowing

1. เปลี่ยนชนิดข้อมูลได้

fn main() {
    let spaces = "   ";        // &str
    let spaces = spaces.len(); // usize

    println!("spaces: {}", spaces); // 3
}

2. แปลงค่าโดยไม่ต้องสร้างชื่อใหม่

fn main() {
    let input = "42";
    let input: i32 = input.parse().unwrap();

    println!("input: {}", input); // 42
}

3. ใช้ใน Scope ที่ต้องการ

fn main() {
    let x = 1;

    {
        let x = 2;
        println!("x in block: {}", x); // 2
    }

    println!("x outside: {}", x); // 1
}

ตัวอย่างการใช้งานจริง

Configuration Constants

const MAX_CONNECTIONS: u32 = 100;
const TIMEOUT_SECONDS: u64 = 30;
const API_VERSION: &str = "v1.0";

fn main() {
    println!("Server config:");
    println!("  Max connections: {}", MAX_CONNECTIONS);
    println!("  Timeout: {} seconds", TIMEOUT_SECONDS);
    println!("  API version: {}", API_VERSION);
}

Mathematical Constants

const PI: f64 = 3.14159265358979323846;
const E: f64 = 2.71828182845904523536;

fn main() {
    let radius = 5.0;
    let area = PI * radius * radius;

    println!("Circle area: {:.2}", area); // 78.54
}

ลองทำดู! 🎯

  1. สร้าง constants สำหรับแอพของคุณ (ชื่อแอพ, เวอร์ชัน)
  2. ลอง shadow ตัวแปรเพื่อแปลง string เป็น number
  3. สร้าง constants สำหรับค่า RGB ของสี

สรุปบทที่ 2

ในบทนี้คุณได้เรียนรู้:

  • ✅ Immutable และ Mutable variables
  • ✅ Scalar types: integers, floats, booleans, characters
  • ✅ Compound types: tuples และ arrays
  • ✅ Constants และ Shadowing

👉 ต่อไป: บทที่ 3: Functions

บทที่ 3: Functions - ฟังก์ชัน

ฟังก์ชัน เป็นหัวใจสำคัญของการเขียนโปรแกรม ช่วยให้เราจัดระเบียบและนำโค้ดกลับมาใช้ซ้ำได้


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
การสร้างฟังก์ชันsyntax และ naming conventions
Parametersการส่งค่าเข้าฟังก์ชัน
Return Valuesการส่งค่ากลับ

ทำไมต้องใช้ฟังก์ชัน?

  1. 🔄 Reusability - ใช้โค้ดซ้ำได้หลายครั้ง
  2. 📦 Organization - จัดระเบียบโค้ดให้อ่านง่าย
  3. 🐛 Debugging - หา bug ได้ง่ายกว่า
  4. 🧪 Testing - ทดสอบแยกส่วนได้

เริ่มกันเลย!

👉 การสร้างฟังก์ชัน

การสร้างฟังก์ชัน

Syntax พื้นฐาน

fn function_name() {
    // body
}

📐 Function Anatomy

+-------------------------------------------------------------------+
|                  Function Anatomy in Rust                         |
+-------------------------------------------------------------------+
|                                                                   |
|   fn   calculate_sum  (  a: i32  ,  b: i32  )  ->  i32   {        |
|   ^         ^            ^--^       ^--^        ^--^      ^       |
|   |         |            |  |       |  |        |  |      |       |
|   |         |            |  type    |  type     |  return |       |
|   |         |            param      param       |  type   body    |
|   |         function name                       return            |
|   keyword                                                         |
|                                                                   |
|       a + b    <--- Expression (no semicolon) = return value      |
|   }                                                               |
|                                                                   |
+-------------------------------------------------------------------+
|   Tip: No semicolon after expression = return that value          |
+-------------------------------------------------------------------+

ตัวอย่าง

fn main() {
    println!("Hello from main!");

    another_function();
}

fn another_function() {
    println!("Hello from another function!");
}

ผลลัพธ์:

Hello from main!
Hello from another function!

Naming Conventions

ใน Rust ใช้ snake_case สำหรับชื่อฟังก์ชัน:

#![allow(unused)]
fn main() {
// ✅ ถูกต้อง - snake_case
fn calculate_area() {}
fn get_user_name() {}
fn process_data() {}

// ❌ ไม่ใช่ convention ของ Rust
fn CalculateArea() {} // PascalCase
fn calculateArea() {} // camelCase
}

ตำแหน่งของฟังก์ชัน

ฟังก์ชันสามารถประกาศไว้ที่ไหนก็ได้ในไฟล์:

fn main() {
    greet(); // เรียกฟังก์ชันที่อยู่ข้างล่าง ✅
}

fn greet() {
    println!("Hello!");
}

หมายเหตุ: ต่างจากบางภาษา (เช่น C) ที่ต้องประกาศฟังก์ชันก่อนเรียกใช้ Rust ไม่สนใจลำดับการประกาศ


ฟังก์ชันซ้อนกัน (ไม่ได้!)

ใน Rust ไม่สามารถประกาศฟังก์ชันซ้อนในฟังก์ชันได้:

// ❌ ไม่ได้
fn outer() {
    fn inner() { // Error!
        println!("Inner");
    }
    inner();
}

เคล็ดลับ: ถ้าต้องการ nested function ใช้ closure แทน:

fn main() {
    // ✅ ใช้ closure แทน nested function
    let greet = |name: &str| {
        println!("Hello, {}!", name);
    };

    greet("Rust");
    greet("World");

    // closure ที่จับตัวแปรจากภายนอก
    let multiplier = 3;
    let multiply = |x: i32| x * multiplier;

    println!("5 * 3 = {}", multiply(5)); // 15
}

ตัวอย่างจริง

fn main() {
    print_welcome_message();
    print_separator();
    print_goodbye_message();
}

fn print_welcome_message() {
    println!("╔═══════════════════════╗");
    println!("║   Welcome to Rust!    ║");
    println!("╚═══════════════════════╝");
}

fn print_separator() {
    println!("─────────────────────────");
}

fn print_goodbye_message() {
    println!("Thanks for learning Rust!");
}

ลองทำดู! 🎯

  1. สร้างฟังก์ชัน print_name ที่พิมพ์ชื่อของคุณ
  2. สร้างฟังก์ชัน print_age ที่พิมพ์อายุ
  3. เรียกใช้ทั้งสองฟังก์ชันจาก main

สรุป

แนวคิดคำอธิบาย
fn name() {}ประกาศฟังก์ชัน
snake_caseNaming convention
ลำดับไม่สำคัญประกาศที่ไหนก็ได้

👉 ต่อไป: Parameters

Parameters (พารามิเตอร์)

Parameters คือค่าที่ส่งเข้าไปในฟังก์ชัน เพื่อให้ฟังก์ชันทำงานกับข้อมูลที่แตกต่างกันได้

Syntax

fn function_name(param1: Type1, param2: Type2) {
    // use param1 and param2
}

สำคัญ: ต้องระบุ ชนิดข้อมูล ของทุก parameter!


ตัวอย่างพื้นฐาน

fn main() {
    print_number(5);
    print_number(10);
}

fn print_number(x: i32) {
    println!("The number is: {}", x);
}

ผลลัพธ์:

The number is: 5
The number is: 10

หลาย Parameters

fn main() {
    print_labeled_measurement(5, 'h');
    greet("Alice", 25);
}

fn print_labeled_measurement(value: i32, unit: char) {
    println!("The measurement is: {}{}", value, unit);
}

fn greet(name: &str, age: u32) {
    println!("Hello, {}! You are {} years old.", name, age);
}

ผลลัพธ์:

The measurement is: 5h
Hello, Alice! You are 25 years old.

Arguments vs Parameters

คำศัพท์คำอธิบาย
Parameterตัวแปรในนิยามฟังก์ชัน
Argumentค่าที่ส่งเข้าไปตอนเรียกใช้
fn add(x: i32, y: i32) { // x, y เป็น parameters
    println!("{}", x + y);
}

fn main() {
    add(5, 3); // 5, 3 เป็น arguments
}

ชนิดข้อมูลที่ใช้บ่อย

fn main() {
    integers(10, -5);
    floats(3.14, 2.5);
    with_string("Hello");
    with_boolean(true);
}

fn integers(a: i32, b: i32) {
    println!("Integers: {} + {} = {}", a, b, a + b);
}

fn floats(a: f64, b: f64) {
    println!("Floats: {} * {} = {}", a, b, a * b);
}

fn with_string(s: &str) {
    println!("String: {}", s);
}

fn with_boolean(flag: bool) {
    println!("Boolean: {}", flag);
}

ตัวอย่างจริง: คำนวณพื้นที่

fn main() {
    let width = 10;
    let height = 5;

    calculate_area(width, height);
}

fn calculate_area(width: u32, height: u32) {
    let area = width * height;
    println!("Width: {}", width);
    println!("Height: {}", height);
    println!("Area: {}", area);
}

ลองทำดู! 🎯

  1. สร้างฟังก์ชัน introduce(name: &str, age: u32) ที่พิมพ์แนะนำตัว
  2. สร้างฟังก์ชัน calculate_bmi(weight: f64, height: f64) ที่คำนวณ BMI
  3. สร้างฟังก์ชัน print_grade(score: u32) ที่พิมพ์เกรด

สรุป

แนวคิดตัวอย่าง
Single parameterfn greet(name: &str)
Multiple parametersfn add(a: i32, b: i32)
ต้องระบุชนิดx: i32 ไม่ใช่แค่ x

👉 ต่อไป: Return Values

Return Values (ค่าที่ส่งกลับ)

ฟังก์ชันสามารถ ส่งค่ากลับ ให้ผู้เรียกใช้ได้

Syntax

fn function_name() -> ReturnType {
    // return value
}
  • ใช้ -> ระบุชนิดค่าที่ส่งกลับ
  • ค่าสุดท้ายใน function body จะถูกส่งกลับ (ไม่ต้องใส่ ;)

ตัวอย่างพื้นฐาน

fn main() {
    let x = five();
    println!("x = {}", x); // 5
}

fn five() -> i32 {
    5 // ไม่มี ; = ส่งค่านี้กลับ
}

Expressions vs Statements

ใน Rust มีแนวคิดสำคัญ:

ประเภทคำอธิบายมีค่า?
Statementทำบางอย่าง ไม่ return ค่า
Expressionประมวลผลและ return ค่า
fn main() {
    // Statement - ไม่มีค่า
    let x = 5; // let ... เป็น statement

    // Expression - มีค่า
    let y = {
        let a = 3;
        a + 1   // ไม่มี ; = expression ที่ return ค่า
    };

    println!("y = {}", y); // 4
}

สำคัญมาก: ถ้าใส่ ; ท้าย จะกลายเป็น statement และไม่ return ค่า!

fn main() {
    let y = {
        let a = 3;
        a + 1; // มี ; = statement = return () (unit)
    };

    // y มีค่าเป็น () ไม่ใช่ 4!
}

ตัวอย่างฟังก์ชันที่ return ค่า

fn main() {
    let sum = add(5, 3);
    println!("5 + 3 = {}", sum); // 8

    let product = multiply(4, 7);
    println!("4 * 7 = {}", product); // 28

    let result = add(multiply(2, 3), 4);
    println!("(2 * 3) + 4 = {}", result); // 10
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

ใช้ return keyword

สามารถใช้ return เพื่อ return ก่อนจบฟังก์ชันได้:

fn main() {
    println!("{}", absolute(-5)); // 5
    println!("{}", absolute(3));  // 3
}

fn absolute(x: i32) -> i32 {
    if x < 0 {
        return -x; // return ก่อนกำหนด
    }
    x // implicit return
}

แนะนำ: ใช้ implicit return (ไม่ใส่ return) เมื่อเป็นไปได้ ใช้ return เมื่อต้อง return ก่อนจบฟังก์ชัน


ฟังก์ชันที่ไม่ return ค่า

ถ้าไม่ระบุ -> ฟังก์ชันจะ return () (unit):

fn main() {
    let result = print_hello();
    println!("result = {:?}", result); // ()
}

fn print_hello() {
    println!("Hello!");
    // implicitly returns ()
}

เทียบเท่ากับ:

#![allow(unused)]
fn main() {
fn print_hello() -> () {
    println!("Hello!");
}
}

ตัวอย่างจริง

fn main() {
    let radius = 5.0;
    let area = circle_area(radius);
    let circumference = circle_circumference(radius);

    println!("Circle with radius {}:", radius);
    println!("  Area: {:.2}", area);
    println!("  Circumference: {:.2}", circumference);
}

fn circle_area(radius: f64) -> f64 {
    3.14159 * radius * radius
}

fn circle_circumference(radius: f64) -> f64 {
    2.0 * 3.14159 * radius
}

ผลลัพธ์:

Circle with radius 5:
  Area: 78.54
  Circumference: 31.42

ลองทำดู! 🎯

  1. สร้างฟังก์ชัน square(x: i32) -> i32 ที่ return ค่ายกกำลังสอง
  2. สร้างฟังก์ชัน max(a: i32, b: i32) -> i32 ที่ return ค่าที่มากกว่า
  3. สร้างฟังก์ชัน is_even(n: i32) -> bool ที่ return true ถ้าเป็นเลขคู่

สรุปบทที่ 3

แนวคิดตัวอย่าง
Return typefn add() -> i32
Implicit returna + b (ไม่มี ;)
Explicit returnreturn value;
Expressionมีค่า, ไม่มี ;
Statementไม่มีค่า, มี ;

👉 ต่อไป: บทที่ 4: Control Flow

บทที่ 4: Control Flow - การควบคุมโปรแกรม

Control Flow ควบคุมลำดับการทำงานของโปรแกรม ให้ทำสิ่งต่างๆ ตามเงื่อนไข หรือทำซ้ำ


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
if/elseเงื่อนไข
Loopsการวนซ้ำ
MatchPattern matching เบื้องต้น

ทำไมต้องเรียน?

โปรแกรมที่มีประโยชน์ต้อง:

  • 🔀 ตัดสินใจ - ทำสิ่งต่างกันตามสถานการณ์
  • 🔁 ทำซ้ำ - ประมวลผลข้อมูลจำนวนมาก
  • ⚡ แยกกรณี - จัดการหลายกรณีพร้อมกัน

เริ่มกันเลย!

👉 if/else

if/else - เงื่อนไข

if ใช้ตัดสินใจว่าจะทำโค้ดส่วนไหน ตามเงื่อนไข

🔀 Control Flow Visualization

+-------------------------------------------------------------------+
|                    if/else Control Flow                           |
+-------------------------------------------------------------------+
|                                                                   |
|                      +-----------+                                |
|                      | condition |                                |
|                      +-----+-----+                                |
|                            |                                      |
|             +--------------+--------------+                       |
|             |              |              |                       |
|             v              v              v                       |
|        +--------+    +----------+    +--------+                   |
|        |  true  |    | else if  |    | false  |                   |
|        | block  |    | (option) |    | block  |                   |
|        +---+----+    +----+-----+    +---+----+                   |
|            |              |              |                        |
|            +--------------+--------------+                        |
|                           |                                       |
|                           v                                       |
|                     continue...                                   |
|                                                                   |
+-------------------------------------------------------------------+
|   Tip: In Rust, no () around condition: if x > 5 (not if (x>5))   |
+-------------------------------------------------------------------+

Syntax พื้นฐาน

if condition {
    // ทำเมื่อ condition เป็น true
}

ตัวอย่าง

fn main() {
    let number = 7;

    if number > 5 {
        println!("{} is greater than 5", number);
    }
}

if-else

fn main() {
    let number = 3;

    if number > 5 {
        println!("Greater than 5");
    } else {
        println!("Less than or equal to 5");
    }
}

else if

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("Divisible by 4");
    } else if number % 3 == 0 {
        println!("Divisible by 3");
    } else if number % 2 == 0 {
        println!("Divisible by 2");
    } else {
        println!("Not divisible by 4, 3, or 2");
    }
}

ผลลัพธ์:

Divisible by 3

หมายเหตุ: Rust จะหยุดเมื่อเจอเงื่อนไขแรกที่ true ดังนั้น 6 หาร 2 ลงตัวด้วย แต่ไม่ถูกพิมพ์


Condition ต้องเป็น bool

ใน Rust condition ต้องเป็น bool:

fn main() {
    let number = 3;

    // ❌ Error! number ไม่ใช่ bool
    // if number {
    //     println!("number is not zero");
    // }

    // ✅ ถูกต้อง
    if number != 0 {
        println!("number is not zero");
    }
}

if ใน let (Ternary-like)

เพราะ if เป็น expression สามารถใส่ใน let ได้:

fn main() {
    let condition = true;

    let number = if condition { 5 } else { 6 };

    println!("number = {}", number); // 5
}

สำคัญ: ทุก branch ต้อง return ชนิดเดียวกัน

fn main() {
    let condition = true;

    // ❌ Error! ชนิดไม่ตรงกัน
    // let number = if condition { 5 } else { "six" };

    // ✅ ต้องเป็นชนิดเดียวกัน
    let number = if condition { 5 } else { 6 };
}

ตัวอย่างจริง: ตรวจสอบอายุ

fn main() {
    let age = 25;

    let category = if age < 13 {
        "Child"
    } else if age < 20 {
        "Teenager"
    } else if age < 60 {
        "Adult"
    } else {
        "Senior"
    };

    println!("Age {} is {}", age, category);
}

ตัวอย่างจริง: ตรวจสอบเกรด

fn main() {
    let score = 85;

    let grade = if score >= 80 {
        'A'
    } else if score >= 70 {
        'B'
    } else if score >= 60 {
        'C'
    } else if score >= 50 {
        'D'
    } else {
        'F'
    };

    println!("Score {} = Grade {}", score, grade);
}

Nested if

fn main() {
    let number = 15;

    if number > 0 {
        if number % 2 == 0 {
            println!("{} is positive and even", number);
        } else {
            println!("{} is positive and odd", number);
        }
    } else {
        println!("{} is not positive", number);
    }
}

ลองทำดู! 🎯

  1. เขียนโปรแกรมตรวจสอบว่าตัวเลขเป็นบวก ลบ หรือศูนย์
  2. เขียนฟังก์ชัน max(a, b) -> i32 ที่ return ค่าที่มากกว่า
  3. เขียนโปรแกรมตรวจสอบปีอธิกสุรทิน

สรุป

แนวคิดตัวอย่าง
ifif x > 0 { ... }
if-elseif x > 0 { ... } else { ... }
else ifelse if x < 0 { ... }
if ใน letlet y = if x > 0 { 1 } else { -1 };

👉 ต่อไป: Loops

Loops - การวนซ้ำ

Rust มี 3 ประเภทของ loop:

Loopใช้เมื่อ
loopวนไม่รู้จบ จนกว่าจะ break
whileวนตราบใดที่เงื่อนไขเป็น true
forวนตามจำนวนที่กำหนด

loop - วนไม่รู้จบ

fn main() {
    let mut count = 0;

    loop {
        count += 1;
        println!("Count: {}", count);

        if count >= 3 {
            break; // ออกจาก loop
        }
    }
}

ผลลัพธ์:

Count: 1
Count: 2
Count: 3

Return ค่าจาก loop

loop สามารถ return ค่าผ่าน break:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2; // return 20
        }
    };

    println!("Result: {}", result); // 20
}

while - วนตามเงื่อนไข

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }

    println!("LIFTOFF!");
}

ผลลัพธ์:

3!
2!
1!
LIFTOFF!

for - วนตาม collection

for เป็น loop ที่ใช้บ่อยที่สุด:

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("Value: {}", element);
    }
}

Range

fn main() {
    // 1 ถึง 4 (ไม่รวม 5)
    for number in 1..5 {
        println!("{}", number);
    }
    // 1, 2, 3, 4

    println!("---");

    // 1 ถึง 5 (รวม 5)
    for number in 1..=5 {
        println!("{}", number);
    }
    // 1, 2, 3, 4, 5
}

Reverse

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!");
}

ผลลัพธ์:

3!
2!
1!
LIFTOFF!

continue - ข้าม iteration

fn main() {
    for number in 1..=10 {
        if number % 2 == 0 {
            continue; // ข้ามเลขคู่
        }
        println!("{}", number);
    }
}

ผลลัพธ์:

1
3
5
7
9

Loop Labels

ใช้ label เพื่อ break/continue loop นอก:

fn main() {
    let mut count = 0;

    'outer: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("  remaining = {}", remaining);
            if remaining == 9 {
                break; // break inner loop
            }
            if count == 2 {
                break 'outer; // break outer loop
            }
            remaining -= 1;
        }

        count += 1;
    }

    println!("End count = {}", count);
}

เปรียบเทียบ Loops

เมื่อไหร่ใช้อะไร?

fn main() {
    // ใช้ for เมื่อรู้จำนวนรอบ หรือวน collection
    for i in 0..5 {
        println!("for: {}", i);
    }

    // ใช้ while เมื่อขึ้นอยู่กับเงื่อนไข
    let mut x = 5;
    while x > 0 {
        println!("while: {}", x);
        x -= 1;
    }

    // ใช้ loop เมื่อต้องการ return ค่า หรือ retry logic
    let result = loop {
        // some logic
        break 42;
    };
    println!("loop result: {}", result);
}

ตัวอย่างจริง: FizzBuzz

fn main() {
    for n in 1..=15 {
        if n % 15 == 0 {
            println!("FizzBuzz");
        } else if n % 3 == 0 {
            println!("Fizz");
        } else if n % 5 == 0 {
            println!("Buzz");
        } else {
            println!("{}", n);
        }
    }
}

ตัวอย่างจริง: หาผลรวม

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let mut sum = 0;

    for n in numbers {
        sum += n;
    }

    println!("Sum: {}", sum); // 15
}

ลองทำดู! 🎯

  1. เขียน loop พิมพ์ตาราง 9
  2. เขียนโปรแกรมหาตัวเลขเฉพาะตั้งแต่ 1-50
  3. เขียน nested loop พิมพ์รูปสามเหลี่ยม *

สรุป

LoopSyntaxใช้เมื่อ
looploop { ... }วนไม่รู้จบ
whilewhile cond { ... }เงื่อนไข
forfor x in iter { ... }collection/range

👉 ต่อไป: Match เบื้องต้น

Match เบื้องต้น

match เป็น control flow ที่ทรงพลังใน Rust ใช้เปรียบเทียบค่ากับ patterns

Syntax พื้นฐาน

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    _ => default_expression,
}

ตัวอย่างพื้นฐาน

fn main() {
    let number = 3;

    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Something else"),
    }
}

ผลลัพธ์:

Three

_ (Underscore) - Catch-all

_ จับทุกกรณีที่ไม่ตรงกับ pattern อื่น:

fn main() {
    let number = 7;

    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Other: {}", number),
    }
}

สำคัญ: match ต้องครอบคลุมทุกกรณี (exhaustive) ถ้าไม่ครบ จะ compile error


match กับ Return ค่า

match เป็น expression สามารถ return ค่าได้:

fn main() {
    let number = 2;

    let text = match number {
        1 => "one",
        2 => "two",
        3 => "three",
        _ => "other",
    };

    println!("{}", text); // two
}

Match หลายค่า

ใช้ | เพื่อ match หลายค่าในครั้งเดียว:

fn main() {
    let number = 2;

    match number {
        1 | 2 | 3 => println!("One, two, or three"),
        4 | 5 | 6 => println!("Four, five, or six"),
        _ => println!("Something else"),
    }
}

Match Range

ใช้ ..= เพื่อ match ช่วงค่า:

fn main() {
    let number = 5;

    match number {
        1..=5 => println!("One through five"),
        6..=10 => println!("Six through ten"),
        _ => println!("Something else"),
    }
}

Match กับ char

fn main() {
    let letter = 'c';

    match letter {
        'a'..='j' => println!("Early letter"),
        'k'..='z' => println!("Late letter"),
        _ => println!("Not a lowercase letter"),
    }
}

match vs if-else

matchif-else
ต้องครอบคลุมทุกกรณีไม่จำเป็น
ใช้ได้กับหลาย patternใช้ได้กับ bool
อ่านง่ายกว่าเมื่อหลายกรณีดีสำหรับ 2-3 กรณี
fn main() {
    let number = 3;

    // match - ชัดเจนกว่า
    let result = match number {
        1 => "one",
        2 => "two",
        3 => "three",
        _ => "other",
    };

    // if-else - ยาวกว่า
    let result2 = if number == 1 {
        "one"
    } else if number == 2 {
        "two"
    } else if number == 3 {
        "three"
    } else {
        "other"
    };
}

ตัวอย่างจริง: วันในสัปดาห์

fn main() {
    let day = 3;

    let day_name = match day {
        1 => "Monday",
        2 => "Tuesday",
        3 => "Wednesday",
        4 => "Thursday",
        5 => "Friday",
        6 | 7 => "Weekend!",
        _ => "Invalid day",
    };

    println!("Day {} is {}", day, day_name);
}

ตัวอย่างจริง: คะแนนเป็นเกรด

fn main() {
    let score = 85;

    let grade = match score {
        90..=100 => 'A',
        80..=89 => 'B',
        70..=79 => 'C',
        60..=69 => 'D',
        0..=59 => 'F',
        _ => '?', // คะแนนไม่ถูกต้อง
    };

    println!("Score {} = Grade {}", score, grade);
}

Multiple Lines ใน Arm

ใช้ { } เมื่อต้องการหลายบรรทัด:

fn main() {
    let number = 2;

    match number {
        1 => {
            println!("Number is one");
            println!("It's the first number");
        }
        2 => {
            println!("Number is two");
            println!("It's the second number");
        }
        _ => println!("Something else"),
    }
}

ลองทำดู! 🎯

  1. เขียน match แปลงเดือน (1-12) เป็นชื่อเดือน
  2. เขียน match จัดกลุ่มอายุ (เด็ก, วัยรุ่น, ผู้ใหญ่, ผู้สูงอายุ)
  3. เขียน match สำหรับ rock-paper-scissors

สรุปบทที่ 4

แนวคิดตัวอย่าง
if/elseif x > 0 { ... } else { ... }
looploop { break; }
whilewhile x > 0 { ... }
forfor x in 1..10 { ... }
matchmatch x { 1 => "one", _ => "other" }

👉 ต่อไป: บทที่ 5: Ownership

บทที่ 5: Ownership - ระบบ Ownership ⭐

Ownership เป็นหัวใจสำคัญของ Rust และเป็นสิ่งที่ทำให้ Rust แตกต่างจากภาษาอื่น!


ทำไมบทนี้สำคัญมาก?

นี่คือบทที่สำคัญที่สุดในหนังสือเล่มนี้!

Ownership คือสิ่งที่ทำให้ Rust:

  • ปลอดภัยจาก memory bugs
  • ไม่ต้องใช้ Garbage Collector
  • มีประสิทธิภาพสูง

ถ้าคุณเข้าใจ Ownership คุณจะเข้าใจ Rust 🦀


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Ownership คืออะไรกฎ 3 ข้อ และหลักการ
Move & Cloneการย้ายและคัดลอก ownership
Referencesการยืมข้อมูล
Slicesการอ้างอิงส่วนหนึ่งของข้อมูล

ปัญหาที่ Ownership แก้

ในภาษาอื่น อาจเจอปัญหาเหล่านี้:

  1. Use after free - ใช้ memory ที่ถูก free ไปแล้ว
  2. Double free - free memory ซ้ำ
  3. Dangling pointers - pointer ชี้ไปที่ที่ไม่มีอยู่
  4. Memory leaks - ลืม free memory

Rust ป้องกันปัญหาทั้งหมดนี้ตอน compile time!


เริ่มกันเลย!

👉 Ownership คืออะไร

Ownership คืออะไร

กฎ 3 ข้อของ Ownership

จำกฎ 3 ข้อนี้ให้ขึ้นใจ:

  1. ทุกค่ามีเจ้าของ (owner) หนึ่งเดียว
  2. ในเวลาใดก็ตาม ค่าหนึ่งมีได้แค่เจ้าของเดียว
  3. เมื่อเจ้าของออกจาก scope ค่าจะถูก drop (ลบ)

⚠️ คำเตือน: ข้อผิดพลาดที่พบบ่อย

  • ❌ ใช้ตัวแปรหลัง move
  • ❌ ส่ง ownership เข้า function แล้วพยายามใช้ต่อ
  • ❌ สร้าง multiple mutable references
  • ✅ ใช้ .clone() เมื่อต้องการ copy จริงๆ
  • ✅ ใช้ references (&) แทน move เมื่อไม่จำเป็นต้องโอน ownership

🔄 Ownership Flow Diagram

+-------------------------------------------------------------------+
|                  Ownership Flow Visualization                     |
+-------------------------------------------------------------------+
|                                                                   |
|   let s1 = String::from("hello");                                 |
|       |                                                           |
|       v                                                           |
|   +-------+                                                       |
|   |  s1   | <-- owner of "hello"                                  |
|   +---+---+                                                       |
|       |                                                           |
|       |  let s2 = s1;  (MOVE)                                     |
|       |                                                           |
|       v                                                           |
|   +-------+     +-------+                                         |
|   |  s1   | --> |  s2   | <-- new owner                           |
|   | DEAD  |     |  OK   |                                         |
|   +-------+     +-------+                                         |
|   (invalid)     (valid)                                           |
|                                                                   |
+-------------------------------------------------------------------+
|  Alternatives:                                                    |
|  1. CLONE: let s2 = s1.clone();  -> s1 OK, s2 OK (both copies)    |
|  2. BORROW: let s2 = &s1;        -> s1 OK, s2 OK (temporary ref)  |
+-------------------------------------------------------------------+

ตัวอย่างพื้นฐาน

fn main() {
    {
        let s = String::from("hello"); // s เกิดขึ้น เป็น owner ของ "hello"

        println!("{}", s); // ใช้ s ได้

    } // s ออกจาก scope -> memory ถูก free อัตโนมัติ

    // println!("{}", s); // ❌ Error! s ไม่มีแล้ว
}

Stack vs Heap

เพื่อเข้าใจ Ownership ต้องเข้าใจ Stack และ Heap ก่อน:

Stack

  • เก็บข้อมูลขนาดคงที่
  • เร็วมาก
  • LIFO (Last In, First Out)
  • เช่น: i32, f64, bool, char

Heap

  • เก็บข้อมูลขนาดไม่คงที่
  • ช้ากว่า Stack
  • ต้อง allocate และ deallocate
  • เช่น: String, Vec<T>
+-----------------+
|     Stack       |
+-----------------+
| ptr  ----------+       +---------------+
| len = 5        |       |     Heap      |
| capacity = 5   |       +---------------+
+----------------+------>| h e l l o     |
                         |               |
                         +---------------+

Copy Types vs Move Types

Copy Types (อยู่บน Stack)

ข้อมูลขนาดเล็กถูก copy อัตโนมัติ:

fn main() {
    let x = 5;
    let y = x; // copy ค่า

    println!("x = {}, y = {}", x, y); // ✅ ทั้งสองใช้ได้
}
Copy Types
i32, u32, i64, etc.
f32, f64
bool
char
Tuples ที่มี Copy types เท่านั้น

Move Types (อยู่บน Heap)

ข้อมูลบน Heap ถูก move:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 ถูก MOVE ไป s2

    // println!("{}", s1); // ❌ Error! s1 ถูก move ไปแล้ว
    println!("{}", s2); // ✅ OK
}

ทำไมต้อง Move?

ถ้า Rust copy ข้อมูลบน Heap แทนที่จะ move:

        +-------------+
s1 ---->|   hello     |<---- s2 (if copy)
        +-------------+

When s1 and s2 go out of scope
-> Both try to free the same memory
-> DOUBLE FREE!

Rust ป้องกันโดยทำให้ s1 ใช้ไม่ได้หลัง move


Ownership และ Functions

ส่งค่าเข้า function = Move

fn main() {
    let s = String::from("hello");

    takes_ownership(s); // s ถูก move เข้า function

    // println!("{}", s); // ❌ Error! s ถูก move ไปแล้ว
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_string ถูก drop ที่นี่

Return = ย้าย ownership กลับ

fn main() {
    let s1 = gives_ownership(); // s1 ได้รับ ownership

    println!("{}", s1); // ✅ OK
}

fn gives_ownership() -> String {
    let s = String::from("hello");
    s // return และ move ownership
}

ตัวอย่าง: ส่งและรับกลับ

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length) // return tuple
}

ปัญหา: ต้อง return ค่ากลับมาถ้าต้องการใช้ต่อ ยุ่งยากมาก!

ทางออก: ใช้ References (บทถัดไป)


Scope และ Drop

fn main() {
    let outer = String::from("outer");

    {
        let inner = String::from("inner");
        println!("{}", inner); // ✅ OK
    } // inner ถูก drop ที่นี่

    // println!("{}", inner); // ❌ inner ไม่มีแล้ว

    println!("{}", outer); // ✅ OK
} // outer ถูก drop ที่นี่

ลองทำดู! 🎯

  1. ลองเขียนโค้ดที่ move String และดู error
  2. แก้ไขโดยใช้ .clone()
  3. ลองส่ง String เข้า function และดูว่าเกิดอะไร

🧠 Advanced: Ownership Edge Cases

Case 1: Partial Move

struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 30,
    };
    
    let name = person.name;  // move name ออกจาก struct
    // println!("{}", person.name);  // ❌ Error! name ถูก move แล้ว
    println!("{}", person.age);      // ✅ OK! age ยังอยู่ (Copy type)
}

Case 2: Reference ใน Struct

// ❌ ไม่ได้! struct เก็บ reference ต้องมี lifetime
struct BadStruct {
    data: &str,  // Error: missing lifetime specifier
}

// ✅ ถูกต้อง
struct GoodStruct<'a> {
    data: &'a str,
}

Case 3: เมื่อใดใช้ Clone vs Reference

สถานการณ์ใช้เหตุผล
ข้อมูลเล็ก ใช้หลายที่.clone()overhead น้อย
ข้อมูลใหญ่ อ่านอย่างเดียว&Tประหยัด memory
ข้อมูลใหญ่ ต้อง modify&mut T หรือ moveขึ้นกับ use case
ส่งข้าม threadsArc<T> + cloneshared ownership

📝 ทดสอบความเข้าใจ

Q1: ทำไม i32 copy ได้แต่ String copy ไม่ได้?

A: i32 เก็บบน stack มีขนาดคงที่ copy ได้เร็ว ส่วน String เก็บ pointer ไปยัง heap ถ้า copy แบบ shallow ทั้งสองตัวแปรจะชี้ไปที่ memory เดียวกัน เกิด double free

Q2: ถ้าต้องการให้หลายตัวแปร "เป็นเจ้าของ" ข้อมูลเดียวกัน ทำยังไง?

A: ใช้ Rc<T> (single thread) หรือ Arc<T> (multi-thread) เพื่อ shared ownership โดยนับ references

Q3: เมื่อไหร่ควรใช้ .clone() vs borrow?

A:

  • Clone: เมื่อ data เล็ก หรือต้องการ ownership แยกจริงๆ
  • Borrow: เมื่อ data ใหญ่ หรือแค่ต้องการอ่าน/เขียนชั่วคราว

สรุป

แนวคิดคำอธิบาย
Ownerตัวแปรที่ “เป็นเจ้าของ” ค่า
Moveย้าย ownership
Dropลบข้อมูลเมื่อออกจาก scope
Stackข้อมูลขนาดเล็ก copy ได้
Heapข้อมูลขนาดใหญ่ต้อง move

👉 ต่อไป: Move & Clone

Move & Clone

Move (การย้าย)

เมื่อ assign ตัวแปร heap type ให้ตัวแปรอื่น ค่าจะถูก move:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // MOVE!

    // s1 invalidated แล้ว
    // println!("{}", s1); // ❌ Error: borrow of moved value

    println!("{}", s2); // ✅ OK
}

แผนภาพ Move

ก่อน Move:
s1 ──────► "hello" (Heap)

หลัง Move:
s1 ──╳──► (invalidated)
s2 ──────► "hello" (Heap)

Clone (การคัดลอก Deep Copy)

ถ้าต้องการ copy ข้อมูลจริงๆ ใช้ .clone():

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone(); // CLONE - deep copy

    println!("s1 = {}", s1); // ✅ OK
    println!("s2 = {}", s2); // ✅ OK
}

แผนภาพ Clone

หลัง Clone:
s1 ──────► "hello" (Heap memory 1)
s2 ──────► "hello" (Heap memory 2)

คำเตือน: .clone() อาจแพงในแง่ performance เพราะต้อง copy ทุก byte


Copy vs Clone

CopyClone
ทำงานอย่างไรAutomatic, bitwise copyต้องเรียก .clone()
ราคาถูก (stack only)อาจแพง (heap copy)
ใช้กับStack typesHeap types
ตัวอย่างi32, boolString, Vec

Copy (อัตโนมัติสำหรับ Stack types)

fn main() {
    let x = 5;
    let y = x; // COPY (อัตโนมัติ)

    println!("x = {}, y = {}", x, y); // ✅ ทั้งสองใช้ได้
}

เมื่อไหร่ใช้ Clone?

1. ต้องการให้ทั้งสองตัวแปรใช้ข้อมูลเดียวกัน

fn main() {
    let original = String::from("hello");
    let backup = original.clone();

    // ใช้ทั้งสองได้
    println!("original: {}", original);
    println!("backup: {}", backup);
}

2. ส่งค่าเข้า function แต่ยังต้องการใช้

fn main() {
    let s = String::from("hello");

    print_string(s.clone()); // ส่ง clone ไป

    println!("Still have: {}", s); // ✅ s ยังใช้ได้
}

fn print_string(s: String) {
    println!("{}", s);
}

ดีกว่า: ใช้ References แทน clone (บทถัดไป)


Clone กับ Collections

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = v1.clone();

    println!("v1: {:?}", v1);
    println!("v2: {:?}", v2);

    // แก้ไข v2 ไม่กระทบ v1
    // (ถ้าทำได้ ต้องเป็น mut)
}

Copy Trait

Types ที่มี Copy trait จะถูก copy อัตโนมัติ:

fn main() {
    // ทั้งหมดนี้มี Copy
    let a: i32 = 5;
    let b = a; // copy

    let c: f64 = 3.14;
    let d = c; // copy

    let e: bool = true;
    let f = e; // copy

    let g: char = 'A';
    let h = g; // copy

    // Tuple ที่มี Copy types
    let tuple1 = (1, 2, 3);
    let tuple2 = tuple1; // copy

    // ใช้ได้ทั้งหมด
    println!("{} {} {} {} {} {}", a, c, e, g, tuple1.0, tuple2.0);
}

สรุป: Move vs Clone vs Copy

fn main() {
    // Copy - Stack types (อัตโนมัติ)
    let x = 5;
    let y = x;
    println!("x={}, y={}", x, y); // ✅

    // Move - Heap types (default)
    let s1 = String::from("hello");
    let s2 = s1;
    // println!("{}", s1); // ❌ moved
    println!("{}", s2); // ✅

    // Clone - Explicit deep copy
    let s3 = String::from("world");
    let s4 = s3.clone();
    println!("s3={}, s4={}", s3, s4); // ✅
}

ลองทำดู! 🎯

  1. สร้าง 2 Strings และลอง move ระหว่างกัน
  2. ใช้ .clone() เพื่อให้ทั้งสองใช้ได้
  3. ลองกับ Vec และ HashMaps

สรุป

การทำงานเมื่อไหร่ผลลัพธ์
CopyStack typesทั้งสองใช้ได้
MoveHeap types (default)ตัวแรกใช้ไม่ได้
Cloneเรียก .clone()ทั้งสอง​ใช้ได้ (คนละ memory)

👉 ต่อไป: References & Borrowing

References & Borrowing

References ช่วยให้เรา “ยืม” ค่า โดยไม่ย้าย ownership

ปัญหา: ต้อง Return ค่ากลับ

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("Length of '{}' is {}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let len = s.len();
    (s, len) // ต้อง return s กลับ 😩
}

ทางออก: ใช้ Reference

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1); // ส่ง reference

    println!("Length of '{}' is {}", s1, len); // s1 ยังใช้ได้!
}

fn calculate_length(s: &String) -> usize { // รับ reference
    s.len()
}

Reference คืออะไร?

Reference คือ pointer ที่ชี้ไปยังค่า โดยไม่เป็นเจ้าของ:

s1 ──────► "hello" (Heap)
           ▲
           │
&s1 ───────┘ (Reference ชี้ไปที่ s1)
  • &s1 สร้าง reference ไปยัง s1
  • &String คือ type ของ reference

กฎของ References (สำคัญมาก!)

กฎ 1: ในเวลาใดก็ตาม มีหนึ่งในสิ่งนี้ได้:

  • หนึ่ง mutable reference (&mut T)
  • กี่อันก็ได้ immutable references (&T)

กฎ 2: References ต้อง valid เสมอ (ไม่ dangling)


Immutable References (&T)

สามารถมีหลาย immutable references พร้อมกันได้:

fn main() {
    let s = String::from("hello");

    let r1 = &s; // ✅
    let r2 = &s; // ✅
    let r3 = &s; // ✅

    println!("{}, {}, {}", r1, r2, r3);
}

แต่ไม่สามารถแก้ไขได้

fn main() {
    let s = String::from("hello");

    let r = &s;

    // r.push_str(" world"); // ❌ Error! cannot borrow as mutable
}

Mutable References (&mut T)

ใช้ &mut เมื่อต้องการแก้ไข:

fn main() {
    let mut s = String::from("hello"); // ต้อง mut

    change(&mut s); // ส่ง mutable reference

    println!("{}", s); // "hello world"
}

fn change(s: &mut String) {
    s.push_str(" world");
}

มีได้แค่หนึ่ง mutable reference

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    // let r2 = &mut s; // ❌ Error! cannot borrow as mutable more than once

    println!("{}", r1);
}

ห้ามผสม Mutable และ Immutable

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;     // immutable borrow
    let r2 = &s;     // immutable borrow
    // let r3 = &mut s; // ❌ Error! cannot borrow as mutable

    println!("{} and {}", r1, r2);
}

แต่ทำได้ถ้า scope ไม่ทับกัน

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // r1 และ r2 ไม่ถูกใช้อีกหลังจากนี้

    let r3 = &mut s; // ✅ OK เพราะ r1, r2 จบแล้ว
    println!("{}", r3);
}

Dangling References

Rust ป้องกัน dangling references:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // ❌ Error!
    let s = String::from("hello");

    &s // reference ไปยัง s
} // s ถูก drop ที่นี่ -> reference จะ invalid!

แก้ไขโดย return ค่าแทน

fn main() {
    let s = no_dangle();
    println!("{}", s);
}

fn no_dangle() -> String { // ✅ Return String แทน
    let s = String::from("hello");
    s // move ownership ออกไป
}

สรุป: Reference Types

Typeความหมายตัวอย่าง
&TImmutable reference&String
&mut TMutable reference&mut String

ตัวอย่างจริง

fn main() {
    let mut message = String::from("Hello");

    // อ่านค่า
    print_length(&message);

    // แก้ไขค่า
    add_world(&mut message);

    println!("Final: {}", message);
}

fn print_length(s: &String) {
    println!("Length: {}", s.len());
}

fn add_world(s: &mut String) {
    s.push_str(", World!");
}

ลองทำดู! 🎯

  1. เขียน function ที่รับ &String และ return ความยาว
  2. เขียน function ที่รับ &mut Vec<i32> และ push ค่าเข้าไป
  3. ลองสร้าง dangling reference และดู error

สรุป

แนวคิดกฎ
&Tอ่านได้อย่างเดียว, มีหลายอันได้
&mut Tแก้ไขได้, มีได้อันเดียว
ห้ามผสม&T และ &mut T พร้อมกันไม่ได้
No danglingReference ต้อง valid เสมอ

👉 ต่อไป: Slices

Slices

Slices ให้เราอ้างอิง ส่วนหนึ่ง ของ collection โดยไม่ต้อง copy

String Slices

String vs Slice Diagram

fn main() {
    let s = String::from("Hello World");

    let hello = &s[0..5];  // "Hello"
    let world = &s[6..11]; // "World"

    println!("{} {}", hello, world);
}

Syntax

&s[start..end]  // start ถึง end-1
&s[start..]     // start ถึงท้าย
&s[..end]       // ต้นถึง end-1
&s[..]          // ทั้งหมด

ตัวอย่าง

fn main() {
    let s = String::from("Hello World");

    let slice1 = &s[0..5];   // "Hello"
    let slice2 = &s[..5];    // "Hello" (เหมือนกัน)

    let slice3 = &s[6..11];  // "World"
    let slice4 = &s[6..];    // "World" (เหมือนกัน)

    let slice5 = &s[0..11];  // "Hello World"
    let slice6 = &s[..];     // "Hello World" (เหมือนกัน)
}

&str Type

&str คือ string slice - reference ไปยังส่วนหนึ่งของ String:

fn main() {
    let s = String::from("Hello World");

    let word: &str = &s[0..5];

    println!("{}", word);
}

String Literals = &str

fn main() {
    let s: &str = "Hello World"; // string literal

    // s เป็น &str ที่ชี้ไปยังข้อมูลใน binary
}

ใช้ Slices กับ Functions

fn main() {
    let sentence = String::from("Hello World");

    let word = first_word(&sentence);

    println!("First word: {}", word);
}

fn first_word(s: &str) -> &str { // รับ &str แทน &String
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

เคล็ดลับ: ใช้ &str เป็น parameter ดีกว่า &String เพราะรับได้ทั้ง String และ &str

fn main() {
    let my_string = String::from("Hello World");

    // ทำงานกับ slice ของ String
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    let word = first_word(&my_string); // deref coercion

    // ทำงานกับ string literal (&str)
    let my_literal = "Hello World";
    let word = first_word(&my_literal[0..6]);
    let word = first_word(my_literal); // ส่งตรงได้เลย
}

fn first_word(s: &str) -> &str {
    // ...
    s
}

Slices ช่วยป้องกัน Bug

โดยไม่มี slices:

fn first_word_index(s: &String) -> usize {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    s.len()
}

fn main() {
    let mut s = String::from("Hello World");

    let word_end = first_word_index(&s); // 5

    s.clear(); // s ว่างเปล่าแล้ว

    // word_end ยังเป็น 5 อยู่ แต่ไม่มีความหมายแล้ว!
    // Bug! 🐛
}

ใช้ slices:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

fn main() {
    let mut s = String::from("Hello World");

    let word = first_word(&s);

    // s.clear(); // ❌ Error! cannot borrow as mutable

    println!("{}", word);
}

Rust ป้องกันไม่ให้แก้ไข s เพราะมี immutable borrow อยู่!


Array Slices

ใช้ได้กับ arrays ด้วย:

fn main() {
    let a = [1, 2, 3, 4, 5];

    let slice: &[i32] = &a[1..3];

    println!("{:?}", slice); // [2, 3]
}

ตัวอย่างจริง

fn main() {
    let text = "The quick brown fox jumps over the lazy dog";

    let words: Vec<&str> = text.split(' ').collect();

    for word in words {
        println!("{}", word);
    }

    // หรือใช้ slice โดยตรง
    let first_three = &text[0..15]; // "The quick brown"
    println!("First part: {}", first_three);
}

ลองทำดู! 🎯

  1. เขียน function ที่ return คำสุดท้ายของ string
  2. เขียน function ที่ return middle element ของ array
  3. ลองใช้ slice กับ Vec

สรุปบทที่ 5

แนวคิดคำอธิบาย
Ownershipทุกค่ามีเจ้าของเดียว
Moveย้าย ownership
CloneCopy ข้อมูล
&TImmutable reference
&mut TMutable reference
SlicesReference ไปส่วนหนึ่งของ collection

🎉 ยินดีด้วย! คุณผ่านบทที่ยากที่สุดแล้ว!

👉 ต่อไป: บทที่ 6: Structs

บทที่ 6: Structs - โครงสร้างข้อมูล

Struct ช่วยให้เราสร้าง custom data type ที่รวมข้อมูลหลายชิ้นไว้ด้วยกัน


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
การสร้าง Structกำหนดโครงสร้างข้อมูล
Methodsฟังก์ชันที่ผูกกับ struct
Associated Functionsฟังก์ชันที่เกี่ยวข้องกับ struct

ทำไมต้องใช้ Struct?

แทนที่จะเขียนแบบนี้:

#![allow(unused)]
fn main() {
let user_name = "Alice";
let user_email = "alice@example.com";
let user_age = 25;
}

ใช้ Struct รวมข้อมูลที่เกี่ยวข้องกัน:

#![allow(unused)]
fn main() {
struct User {
    name: String,
    email: String,
    age: u32,
}
}

เริ่มกันเลย!

👉 การสร้าง Struct

การสร้าง Struct

นิยาม Struct

#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
}
  • ใช้ struct keyword
  • ตั้งชื่อแบบ PascalCase (ตัวพิมพ์ใหญ่ขึ้นต้นแต่ละคำ)
  • แต่ละ field มี name: Type

📦 Struct Memory Layout

Struct Memory Layout


สร้าง Instance

fn main() {
    let user1 = User {
        email: String::from("alice@example.com"),
        username: String::from("alice"),
        active: true,
        sign_in_count: 1,
    };

    println!("Username: {}", user1.username);
    println!("Email: {}", user1.email);
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

เข้าถึงและแก้ไข Fields

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let mut user1 = User {
        email: String::from("alice@example.com"),
        username: String::from("alice"),
        active: true,
        sign_in_count: 1,
    };

    // อ่านค่า
    println!("Email: {}", user1.email);

    // แก้ไขค่า (ต้อง mut)
    user1.email = String::from("new@example.com");

    println!("New email: {}", user1.email);
}

หมายเหตุ: ทั้ง instance ต้องเป็น mut ไม่สามารถ mut แค่บาง field ได้


Field Init Shorthand

ถ้าชื่อ parameter ตรงกับ field ไม่ต้องเขียนซ้ำ:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,    // แทน email: email
        username, // แทน username: username
        active: true,
        sign_in_count: 1,
    }
}

Struct Update Syntax

สร้าง instance ใหม่จาก instance เดิม:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("alice@example.com"),
        username: String::from("alice"),
        active: true,
        sign_in_count: 1,
    };

    // สร้าง user2 โดยใช้ค่าจาก user1
    let user2 = User {
        email: String::from("bob@example.com"),
        ..user1 // ใช้ค่าที่เหลือจาก user1
    };

    // ⚠️ user1.username ถูก move ไป user2 แล้ว!
    // println!("{}", user1.username); // ❌ Error
    println!("{}", user1.email); // ✅ OK (ไม่ได้ move)
}

Tuple Structs

Struct ที่ไม่มีชื่อ field:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    println!("R: {}", black.0);
    println!("X: {}", origin.0);
}

หมายเหตุ: Color และ Point เป็นคนละ type แม้จะมีโครงสร้างเหมือนกัน


Unit-like Structs

Struct ที่ไม่มี field:

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

มีประโยชน์เมื่อต้องการ implement trait โดยไม่ต้องเก็บข้อมูล


Debug Printing

เพิ่ม #[derive(Debug)] เพื่อ print struct:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("{:?}", rect);   // Debug format
    println!("{:#?}", rect);  // Pretty debug format
}

Output:

Rectangle { width: 30, height: 50 }
Rectangle {
    width: 30,
    height: 50,
}

ลองทำดู! 🎯

  1. สร้าง struct Book ที่มี title, author, pages
  2. สร้าง struct Point สำหรับพิกัด 2D
  3. ใช้ #[derive(Debug)] และ print struct

สรุป

แนวคิดตัวอย่าง
Definestruct Name { field: Type }
CreateName { field: value }
Accessinstance.field
Shorthandfield แทน field: field
Update..other_instance
Debug#[derive(Debug)]

👉 ต่อไป: Methods

Methods

Methods คือ functions ที่ผูกกับ struct ใช้ impl block

นิยาม Method

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("Area: {}", rect.area());
}

&self Parameter

&self เป็น shorthand สำหรับ self: &Self:

Parameterความหมาย
&selfยืม (อ่านอย่างเดียว)
&mut selfยืมแบบแก้ไขได้
selfรับ ownership
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // อ่านค่าอย่างเดียว
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // แก้ไขค่า
    fn double_width(&mut self) {
        self.width *= 2;
    }

    // รับ ownership (หลังเรียก instance จะใช้ไม่ได้)
    fn destroy(self) {
        println!("Destroying {:?}", self);
    }
}

Methods ที่มี Parameters

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("rect1 can hold rect2: {}", rect1.can_hold(&rect2)); // true
    println!("rect1 can hold rect3: {}", rect1.can_hold(&rect3)); // false
}

หลาย impl Blocks

สามารถแยก methods เป็นหลาย impl blocks ได้:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }
}

Method Chaining

ออกแบบ methods ที่ return &mut self เพื่อ chain ได้:

#[derive(Debug)]
struct Counter {
    value: i32,
}

impl Counter {
    fn new() -> Counter {
        Counter { value: 0 }
    }

    fn increment(&mut self) -> &mut Self {
        self.value += 1;
        self
    }

    fn add(&mut self, n: i32) -> &mut Self {
        self.value += n;
        self
    }
}

fn main() {
    let mut counter = Counter::new();

    counter
        .increment()
        .increment()
        .add(10);

    println!("{:?}", counter); // Counter { value: 12 }
}

Automatic Referencing

Rust จะเพิ่ม &, &mut, หรือ * อัตโนมัติเมื่อเรียก method:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };

    // เทียบเท่ากัน:
    rect.area();
    (&rect).area();
}

ตัวอย่างจริง: Circle

use std::f64::consts::PI;

#[derive(Debug)]
struct Circle {
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }

    fn circumference(&self) -> f64 {
        2.0 * PI * self.radius
    }

    fn grow(&mut self, amount: f64) {
        self.radius += amount;
    }
}

fn main() {
    let mut circle = Circle { radius: 5.0 };

    println!("Radius: {}", circle.radius);
    println!("Area: {:.2}", circle.area());
    println!("Circumference: {:.2}", circle.circumference());

    circle.grow(2.5);
    println!("After grow - Area: {:.2}", circle.area());
}

ลองทำดู! 🎯

  1. เพิ่ม method is_square(&self) ให้ Rectangle
  2. สร้าง struct BankAccount พร้อม methods deposit, withdraw
  3. สร้าง method chain สำหรับ builder pattern

สรุป

แนวคิดตัวอย่าง
Methodfn method(&self) {}
Mutablefn method(&mut self) {}
With argsfn method(&self, arg: Type) {}
impl blockimpl StructName { ... }

👉 ต่อไป: Associated Functions

Associated Functions

Associated Functions คือ functions ใน impl block ที่ไม่มี self เรียกด้วย ::

Syntax

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::new(30, 50); // ใช้ :: ไม่ใช่ .

    println!("{:?}", rect);
}

Constructor Pattern

Associated functions มักใช้เป็น constructor:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Constructor หลัก
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    // Constructor สำหรับสี่เหลี่ยมจัตุรัส
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }

    // Constructor default
    fn default() -> Self {
        Self {
            width: 1,
            height: 1,
        }
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    let square = Rectangle::square(10);
    let default = Rectangle::default();

    println!("{:?}", rect);
    println!("{:?}", square);
    println!("{:?}", default);
}

หมายเหตุ: ใช้ Self แทนชื่อ struct ได้ใน impl block


Methods vs Associated Functions

MethodsAssociated Functions
มี &self, &mut self, selfไม่มี self
เรียกด้วย .เรียกด้วย ::
เข้าถึง instance data ได้เข้าถึง instance data ไม่ได้
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Associated Function (ไม่มี self)
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    // Method (มี &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(10, 20); // ::
    let area = rect.area();             // .
}

ตัวอย่าง: String::from

String::from เป็น associated function ที่เราใช้บ่อย:

fn main() {
    let s = String::from("hello"); // associated function

    let len = s.len(); // method
}

ตัวอย่างจริง: User

#[derive(Debug)]
struct User {
    username: String,
    email: String,
    active: bool,
}

impl User {
    // Constructor
    fn new(username: String, email: String) -> Self {
        Self {
            username,
            email,
            active: true,
        }
    }

    // สร้างจาก email (extract username)
    fn from_email(email: String) -> Self {
        let username = email
            .split('@')
            .next()
            .unwrap_or("unknown")
            .to_string();

        Self::new(username, email)
    }

    // Methods
    fn is_active(&self) -> bool {
        self.active
    }

    fn deactivate(&mut self) {
        self.active = false;
    }
}

fn main() {
    let user1 = User::new(
        String::from("alice"),
        String::from("alice@example.com")
    );

    let user2 = User::from_email(String::from("bob@example.com"));

    println!("{:?}", user1);
    println!("{:?}", user2);
}

Builder Pattern

ใช้ associated functions และ methods ร่วมกัน:

#[derive(Debug)]
struct Car {
    brand: String,
    model: String,
    year: u32,
    color: String,
}

impl Car {
    fn builder() -> CarBuilder {
        CarBuilder::default()
    }
}

#[derive(Default)]
struct CarBuilder {
    brand: String,
    model: String,
    year: u32,
    color: String,
}

impl CarBuilder {
    fn brand(mut self, brand: &str) -> Self {
        self.brand = brand.to_string();
        self
    }

    fn model(mut self, model: &str) -> Self {
        self.model = model.to_string();
        self
    }

    fn year(mut self, year: u32) -> Self {
        self.year = year;
        self
    }

    fn color(mut self, color: &str) -> Self {
        self.color = color.to_string();
        self
    }

    fn build(self) -> Car {
        Car {
            brand: self.brand,
            model: self.model,
            year: self.year,
            color: self.color,
        }
    }
}

fn main() {
    let car = Car::builder()
        .brand("Toyota")
        .model("Camry")
        .year(2024)
        .color("Blue")
        .build();

    println!("{:?}", car);
}

ลองทำดู! 🎯

  1. สร้าง Point::origin() ที่ return Point(0, 0)
  2. สร้าง Circle::with_radius(r) constructor
  3. Implement builder pattern สำหรับ struct ที่คุณสร้าง

สรุปบทที่ 6

แนวคิดตัวอย่าง
Structstruct Name { field: Type }
Methodfn method(&self)
Associated Functionfn func() -> Self
Constructorfn new(...) -> Self
BuilderChain methods ที่ return Self

👉 ต่อไป: บทที่ 7: Enums & Pattern Matching

บทที่ 7: Enums & Pattern Matching

Enums ช่วยให้เราแสดงค่าที่เป็นไปได้หลายแบบ และ Pattern Matching ช่วยจัดการแต่ละแบบ


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
การสร้าง Enumนิยาม variants
Option<T>จัดการค่าที่อาจไม่มี
MatchPattern matching
if letConcise matching

Enums คืออะไร?

Enum แสดงว่าค่าเป็น หนึ่งใน หลายตัวเลือก:

#![allow(unused)]
fn main() {
enum Direction {
    North,
    South,
    East,
    West,
}
}

ค่าของ Direction ต้องเป็น North, South, East, หรือ West เท่านั้น


เริ่มกันเลย!

👉 การสร้าง Enum

การสร้าง Enum

Syntax พื้นฐาน

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(four);
    route(six);
}

fn route(ip_kind: IpAddrKind) {
    // ...
}

Enum กับ Data

Variants สามารถมีข้อมูลแนบได้:

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

fn main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
}

Variants หลายแบบ

แต่ละ variant มีข้อมูลต่างกันได้:

enum Message {
    Quit,                       // ไม่มีข้อมูล
    Move { x: i32, y: i32 },    // anonymous struct
    Write(String),              // String
    ChangeColor(i32, i32, i32), // 3 integers
}

fn main() {
    let m1 = Message::Quit;
    let m2 = Message::Move { x: 10, y: 20 };
    let m3 = Message::Write(String::from("Hello"));
    let m4 = Message::ChangeColor(255, 0, 0);
}

Methods บน Enum

เหมือนกับ struct:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // method body
        println!("Message received!");
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

เปรียบเทียบ Enum vs Struct

ใช้ Struct + Enum

enum IpAddrKind {
    V4,
    V6,
}

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
}

ใช้ Enum alone (ดีกว่า!)

enum IpAddr {
    V4(String),
    V6(String),
}

fn main() {
    let home = IpAddr::V4(String::from("127.0.0.1"));
}

ตัวอย่างจริง: WebEvent

enum WebEvent {
    PageLoad,
    PageUnload,
    KeyPress(char),
    Paste(String),
    Click { x: i64, y: i64 },
}

fn inspect(event: WebEvent) {
    match event {
        WebEvent::PageLoad => println!("Page loaded"),
        WebEvent::PageUnload => println!("Page unloaded"),
        WebEvent::KeyPress(c) => println!("Key pressed: {}", c),
        WebEvent::Paste(s) => println!("Pasted: {}", s),
        WebEvent::Click { x, y } => println!("Clicked at ({}, {})", x, y),
    }
}

fn main() {
    let press = WebEvent::KeyPress('x');
    let click = WebEvent::Click { x: 20, y: 80 };

    inspect(press);
    inspect(click);
}

ลองทำดู! 🎯

  1. สร้าง enum TrafficLight (Red, Yellow, Green)
  2. สร้าง enum Shape ที่มี Circle(radius), Rectangle(w, h)
  3. เพิ่ม method area() ให้ Shape

สรุป

แนวคิดตัวอย่าง
Basic enumenum Name { A, B }
With dataenum Name { A(i32) }
Named fieldsenum Name { A { x: i32 } }
UseName::A

👉 ต่อไป: Option<T>

Option<T>

Option<T> เป็น enum มาตรฐานที่ใช้แทน null ในภาษาอื่น

ปัญหาของ Null

ในภาษาอื่น null ทำให้เกิด bugs มากมาย:

// JavaScript
let name = null;
console.log(name.length); // 💥 Crash!

Rust ไม่มี null แต่ใช้ Option<T> แทน


นิยามของ Option

#![allow(unused)]
fn main() {
enum Option<T> {
    None,    // ไม่มีค่า
    Some(T), // มีค่า
}
}

หมายเหตุ: Option, Some, None อยู่ใน prelude ใช้ได้เลยโดยไม่ต้อง import


การใช้งาน Option

fn main() {
    let some_number: Option<i32> = Some(5);
    let some_string: Option<&str> = Some("hello");
    let absent_number: Option<i32> = None;

    println!("{:?}", some_number);  // Some(5)
    println!("{:?}", some_string);  // Some("hello")
    println!("{:?}", absent_number); // None
}

ทำไม Option ดีกว่า Null?

Compiler บังคับให้จัดการ

fn main() {
    let x: i32 = 5;
    let y: Option<i32> = Some(5);

    // let sum = x + y; // ❌ Error! cannot add i32 and Option<i32>
}

ต้อง “แกะ” Option ก่อนใช้ → บังคับให้คิดเรื่อง None


การจัดการ Option

1. match

fn main() {
    let x: Option<i32> = Some(5);

    match x {
        Some(value) => println!("Value: {}", value),
        None => println!("No value"),
    }
}

2. if let

fn main() {
    let x: Option<i32> = Some(5);

    if let Some(value) = x {
        println!("Value: {}", value);
    }
}

3. unwrap (ระวัง!)

fn main() {
    let x: Option<i32> = Some(5);
    let value = x.unwrap(); // ✅ ได้ 5

    let y: Option<i32> = None;
    // let value2 = y.unwrap(); // 💥 Panic!
}

4. unwrap_or (ปลอดภัยกว่า)

fn main() {
    let x: Option<i32> = Some(5);
    let y: Option<i32> = None;

    println!("{}", x.unwrap_or(0)); // 5
    println!("{}", y.unwrap_or(0)); // 0 (default)
}

5. map

fn main() {
    let x: Option<i32> = Some(5);

    let doubled = x.map(|v| v * 2);

    println!("{:?}", doubled); // Some(10)
}

Methods ที่ใช้บ่อย

Methodคำอธิบาย
is_some()return true ถ้า Some
is_none()return true ถ้า None
unwrap()ดึงค่าออก (panic ถ้า None)
unwrap_or(default)ดึงค่า หรือใช้ default
unwrap_or_else(f)ดึงค่า หรือ call function
map(f)แปลงค่าข้างใน
and_then(f)chain Options
fn main() {
    let x: Option<i32> = Some(5);
    let y: Option<i32> = None;

    println!("x is_some: {}", x.is_some()); // true
    println!("y is_none: {}", y.is_none()); // true

    // Chain operations
    let result = x
        .map(|v| v * 2)      // Some(10)
        .map(|v| v + 1)      // Some(11)
        .unwrap_or(0);       // 11

    println!("Result: {}", result);
}

ตัวอย่างจริง: หาค่าใน Array

fn find_item(items: &[i32], target: i32) -> Option<usize> {
    for (index, &item) in items.iter().enumerate() {
        if item == target {
            return Some(index);
        }
    }
    None
}

fn main() {
    let numbers = [1, 2, 3, 4, 5];

    match find_item(&numbers, 3) {
        Some(index) => println!("Found at index {}", index),
        None => println!("Not found"),
    }
}

ตัวอย่างจริง: Division

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    let result1 = divide(10.0, 2.0);
    let result2 = divide(10.0, 0.0);

    println!("{:?}", result1); // Some(5.0)
    println!("{:?}", result2); // None
}

ลองทำดู! 🎯

  1. เขียน function first(vec: &Vec<i32>) -> Option<i32>
  2. เขียน function parse_number(s: &str) -> Option<i32>
  3. Chain หลาย Option methods ด้วยกัน

สรุป

แนวคิดตัวอย่าง
SomeSome(5)
NoneNone
Matchmatch opt { Some(x) => ..., None => ... }
unwrap_oropt.unwrap_or(default)
mapopt.map(|x| x * 2)

👉 ต่อไป: Match Expression

Match Expression

match เป็น control flow ที่ทรงพลังใน Rust ใช้เปรียบเทียบค่ากับหลาย patterns

Syntax

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    _ => default,
}

Match กับ Enum

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {
    let coin = Coin::Dime;
    println!("Value: {} cents", value_in_cents(coin));
}

Exhaustive Matching

match ต้องครอบคลุมทุกกรณี:

#![allow(unused)]
fn main() {
enum Color {
    Red,
    Green,
    Blue,
}

fn describe(color: Color) {
    match color {
        Color::Red => println!("Red"),
        Color::Green => println!("Green"),
        // ❌ Error: pattern `Blue` not covered
    }
}
}

แก้ไข

#![allow(unused)]
fn main() {
enum Color {
    Red,
    Green,
    Blue,
}

fn describe(color: Color) {
    match color {
        Color::Red => println!("Red"),
        Color::Green => println!("Green"),
        Color::Blue => println!("Blue"),
    }
}
}

_ (Catch-all)

ใช้ _ จับ patterns ที่เหลือ:

fn main() {
    let number = 13;

    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Something else"),
    }
}

Match กับ Binding

ดึงค่าจาก enum:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => {
            println!("Move to ({}, {})", x, y);
        }
        Message::Write(text) => {
            println!("Text: {}", text);
        }
    }
}

fn main() {
    process(Message::Move { x: 10, y: 20 });
    process(Message::Write(String::from("Hello")));
}

Match กับ Option

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    println!("{:?}", six);  // Some(6)
    println!("{:?}", none); // None
}

Match Guards

เพิ่มเงื่อนไขให้ pattern:

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x < 5 => println!("Less than 5: {}", x),
        Some(x) => println!("5 or more: {}", x),
        None => println!("None"),
    }
}

@ Binding

จับค่าพร้อมทั้งตรวจ pattern:

fn main() {
    let msg = Message::Move { x: 5, y: 10 };

    match msg {
        Message::Move { x: x_val @ 0..=10, y } => {
            println!("x ({}) is in range, y = {}", x_val, y);
        }
        Message::Move { x, y } => {
            println!("x ({}) is out of range, y = {}", x, y);
        }
        _ => {}
    }
}

enum Message {
    Move { x: i32, y: i32 },
}

Multiple Patterns

ใช้ | เพื่อ match หลาย patterns:

fn main() {
    let n = 3;

    match n {
        1 | 2 | 3 => println!("One, two, or three"),
        4..=10 => println!("Four to ten"),
        _ => println!("Something else"),
    }
}

Match เป็น Expression

fn main() {
    let x = 1;

    let message = match x {
        1 => "one",
        2 => "two",
        _ => "other",
    };

    println!("{}", message);
}

ตัวอย่างจริง: Command Processing

enum Command {
    Start,
    Stop,
    Speed(u32),
    Position { x: i32, y: i32 },
}

fn execute(cmd: Command) -> String {
    match cmd {
        Command::Start => String::from("Starting..."),
        Command::Stop => String::from("Stopping..."),
        Command::Speed(s) if s > 100 => format!("Too fast! {}", s),
        Command::Speed(s) => format!("Setting speed to {}", s),
        Command::Position { x, y } => format!("Moving to ({}, {})", x, y),
    }
}

fn main() {
    println!("{}", execute(Command::Start));
    println!("{}", execute(Command::Speed(50)));
    println!("{}", execute(Command::Speed(150)));
    println!("{}", execute(Command::Position { x: 10, y: 20 }));
}

ลองทำดู! 🎯

  1. เขียน match สำหรับ Option<String> ที่ print ความยาว
  2. เขียน match ที่ใช้ guard เช็คค่าบวก/ลบ
  3. เขียน function ที่ return String ด้วย match

สรุป

แนวคิดตัวอย่าง
Basicmatch x { 1 => ..., _ => ... }
BindingSome(value) => ...
Multiple1 | 2 | 3 => ...
GuardSome(x) if x > 0 => ...
@ Bindingx @ 1..=10 => ...

👉 ต่อไป: if let & while let

if let & while let

if let และ while let เป็นทางเลือกที่กระชับสำหรับ match เมื่อสนใจแค่ pattern เดียว

ปัญหา: Match ยาวไป

fn main() {
    let some_value: Option<i32> = Some(3);

    // ยาวเกินไปสำหรับแค่ pattern เดียว
    match some_value {
        Some(value) => println!("Value: {}", value),
        _ => (),  // ไม่ทำอะไร
    }
}

if let

fn main() {
    let some_value: Option<i32> = Some(3);

    // กระชับกว่า!
    if let Some(value) = some_value {
        println!("Value: {}", value);
    }
}

Syntax

if let PATTERN = EXPRESSION {
    // ทำเมื่อ match
}

หรือพร้อม else:

if let PATTERN = EXPRESSION {
    // ทำเมื่อ match
} else {
    // ทำเมื่อไม่ match
}

if let กับ else

fn main() {
    let some_value: Option<i32> = None;

    if let Some(value) = some_value {
        println!("Value: {}", value);
    } else {
        println!("No value!");
    }
}

เทียบเท่ากับ:

match some_value {
    Some(value) => println!("Value: {}", value),
    _ => println!("No value!"),
}

if let กับ Enum

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

fn main() {
    let msg = Message::Move { x: 10, y: 20 };

    if let Message::Move { x, y } = msg {
        println!("Moving to ({}, {})", x, y);
    }

    let msg2 = Message::Write(String::from("hello"));

    if let Message::Write(text) = msg2 {
        println!("Message: {}", text);
    }
}

while let

ทำซ้ำตราบใดที่ pattern ยัง match:

fn main() {
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    // pop() returns Option<T>
    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
}

Output:

3
2
1

ตัวอย่าง: Iterator

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let mut iter = numbers.iter();

    while let Some(num) = iter.next() {
        println!("{}", num);
    }
}

let else (Rust 1.65+)

Unwrap หรือ return เร็ว:

fn get_length(s: Option<String>) -> usize {
    let Some(text) = s else {
        return 0;  // ต้อง return, break, continue, panic
    };

    text.len()
}

fn main() {
    println!("{}", get_length(Some(String::from("hello")))); // 5
    println!("{}", get_length(None)); // 0
}

เปรียบเทียบ

matchif let
ครอบคลุมทุก patternpattern เดียว
Exhaustiveไม่ exhaustive
ยาวกว่ากระชับกว่า

ใช้ match เมื่อ:

  • มีหลาย patterns
  • ต้องการให้ compiler บังคับครบทุกกรณี

ใช้ if let เมื่อ:

  • สนใจแค่ pattern เดียว
  • ต้องการความกระชับ

ตัวอย่างจริง

fn main() {
    let config_max: Option<u8> = Some(100);

    // if let - กระชับ
    if let Some(max) = config_max {
        println!("Maximum is {}", max);
    }

    // Regular if - ใช้ไม่ได้!
    // if config_max.is_some() {
    //     let max = config_max.unwrap();  // clunky
    //     println!("Maximum is {}", max);
    // }
}

Chained if let

fn main() {
    let maybe_number: Option<i32> = Some(42);
    let maybe_string: Option<&str> = Some("hello");

    if let Some(n) = maybe_number {
        if let Some(s) = maybe_string {
            println!("Number: {}, String: {}", n, s);
        }
    }
}

หรือใช้ && (Rust 1.53+):

fn main() {
    let maybe_number: Option<i32> = Some(42);
    let maybe_string: Option<&str> = Some("hello");

    if let (Some(n), Some(s)) = (maybe_number, maybe_string) {
        println!("Number: {}, String: {}", n, s);
    }
}

ลองทำดู! 🎯

  1. เขียน if let สำหรับ Option<String>
  2. ใช้ while let pop จาก Vec
  3. แปลง match ที่มี 2 arms เป็น if let else

สรุปบทที่ 7

แนวคิดตัวอย่าง
Enumenum Name { A, B(T) }
OptionSome(x), None
matchmatch x { A => ..., B => ... }
if letif let Some(x) = opt { ... }
while letwhile let Some(x) = iter.next() { ... }

👉 ต่อไป: บทที่ 8: Collections

บทที่ 8: Collections - คอลเลกชัน

Collections เก็บข้อมูลหลายค่า ต่างจาก array/tuple ที่ขนาดคงที่ collections ขยายได้


สิ่งที่จะได้เรียนรู้

Collectionคำอธิบาย
Vec<T>Dynamic array
StringUTF-8 text
HashMap<K, V>Key-value pairs

เมื่อไหร่ใช้อะไร?

ต้องการใช้
List ที่ขยายได้Vec\<T\>
TextString
Key-value lookupHashMap\<K, V\>

เริ่มกันเลย!

👉 Vec<T>

Vec<T> - Vector

Vector เป็น dynamic array ที่ขยายขนาดได้

📊 Collections Comparison

Collectionเมื่อไหร่ใช้Key Feature
Vec<T>ลำดับข้อมูล, dynamic sizeIndex access O(1)
StringText dataUTF-8 encoded
HashMap<K,V>Key-value pairsLookup O(1)
HashSet<T>Unique valuesDedup, membership
VecDeque<T>Queue/DequePush/pop both ends

📦 Vector Memory Layout

+-------------------------------------------------------------------+
|                    Vec<T> Memory Structure                        |
+-------------------------------------------------------------------+
|                                                                   |
|   Stack                          Heap                             |
|   -----                          ----                             |
|   +-------------+                                                 |
|   | ptr --------+--------------> +---+---+---+---+---+            |
|   | len = 3     |                | 1 | 2 | 3 |   |   |            |
|   | capacity = 5|                +---+---+---+---+---+            |
|   +-------------+                  ^           ^                  |
|                                    |           |                  |
|                              used (len)   allocated (capacity)    |
|                                                                   |
+-------------------------------------------------------------------+

สร้าง Vector

fn main() {
    // สร้าง vector ว่าง
    let v1: Vec<i32> = Vec::new();

    // สร้างด้วย vec! macro
    let v2 = vec![1, 2, 3];

    println!("{:?}", v1); // []
    println!("{:?}", v2); // [1, 2, 3]
}

เพิ่มข้อมูล

fn main() {
    let mut v = Vec::new();

    v.push(1);
    v.push(2);
    v.push(3);

    println!("{:?}", v); // [1, 2, 3]
}

เข้าถึงข้อมูล

แบบที่ 1: Indexing

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third = &v[2];
    println!("Third element: {}", third);

    // ⚠️ panic ถ้า index ไม่มี!
    // let hundred = &v[100]; // 💥 panic!
}

แบบที่ 2: get() (ปลอดภัยกว่า)

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    match v.get(2) {
        Some(value) => println!("Third: {}", value),
        None => println!("No element"),
    }

    // v.get(100) returns None (ไม่ panic)
    if let Some(value) = v.get(100) {
        println!("Found: {}", value);
    } else {
        println!("Not found");
    }
}

วนลูป

Immutable iteration

fn main() {
    let v = vec![1, 2, 3];

    for i in &v {
        println!("{}", i);
    }

    println!("v still accessible: {:?}", v);
}

Mutable iteration

fn main() {
    let mut v = vec![1, 2, 3];

    for i in &mut v {
        *i *= 2; // dereference with *
    }

    println!("{:?}", v); // [2, 4, 6]
}

Methods ที่ใช้บ่อย

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    println!("len: {}", v.len());       // 5
    println!("is_empty: {}", v.is_empty()); // false
    println!("first: {:?}", v.first()); // Some(1)
    println!("last: {:?}", v.last());   // Some(5)

    // pop - ลบและ return ตัวสุดท้าย
    let last = v.pop();
    println!("popped: {:?}", last); // Some(5)
    println!("v: {:?}", v); // [1, 2, 3, 4]

    // insert - แทรกที่ตำแหน่ง
    v.insert(1, 10);
    println!("after insert: {:?}", v); // [1, 10, 2, 3, 4]

    // remove - ลบที่ตำแหน่ง
    let removed = v.remove(1);
    println!("removed: {}", removed); // 10
    println!("after remove: {:?}", v); // [1, 2, 3, 4]

    // contains
    println!("contains 3: {}", v.contains(&3)); // true
}

Vector กับ Enum

เก็บหลาย types ด้วย enum:

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

fn main() {
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Float(10.12),
        SpreadsheetCell::Text(String::from("hello")),
    ];

    for cell in &row {
        match cell {
            SpreadsheetCell::Int(i) => println!("Int: {}", i),
            SpreadsheetCell::Float(f) => println!("Float: {}", f),
            SpreadsheetCell::Text(s) => println!("Text: {}", s),
        }
    }
}

Ownership และ Vector

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let first = &v[0]; // borrow

    // v.push(6); // ❌ Error! cannot borrow v as mutable

    println!("First: {}", first);
}

ทำไม? เพราะ push อาจทำให้ vector ย้ายที่ในเมมโมรี่ ทำให้ reference เดิมไม่ valid


ลองทำดู! 🎯

  1. สร้าง vector ของเลข 1-10 แล้วคำนวณผลรวม
  2. สร้าง function ที่รับ &mut Vec<i32> และกรองเอาเฉพาะเลขคู่
  3. ใช้ enum เก็บหลาย types ใน vector

สรุป

Methodคำอธิบาย
Vec::new()สร้าง vector ว่าง
vec![...]สร้าง vector พร้อมค่า
push(x)เพิ่มท้าย
pop()ลบท้าย
v[i]เข้าถึง (panic ได้)
v.get(i)เข้าถึง (ปลอดภัย)
for x in &viterate

👉 ต่อไป: String

String

String ใน Rust มี 2 แบบหลัก:

Typeคำอธิบาย
StringOwned, mutable, heap-allocated
&strBorrowed, immutable, slice

สร้าง String

fn main() {
    // จาก string literal
    let s1 = String::from("Hello");
    let s2 = "Hello".to_string();

    // String ว่าง
    let s3 = String::new();

    // จาก format!
    let s4 = format!("Hello, {}!", "World");

    println!("{}, {}, {}, {}", s1, s2, s3, s4);
}

String vs &str

fn main() {
    let s1: String = String::from("Hello"); // owned
    let s2: &str = "Hello";                 // borrowed (string literal)
    let s3: &str = &s1;                     // borrowed from String

    // String -> &str (automatic coercion)
    greet(&s1);
    greet(s2);

    // &str -> String
    let s4: String = s2.to_string();
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

แนะนำ: ใช้ &str เป็น parameter, ใช้ String เมื่อต้อง own ข้อมูล


ต่อ String

push_str และ push

fn main() {
    let mut s = String::from("Hello");

    s.push_str(", World"); // ต่อ string
    s.push('!');           // ต่อ char

    println!("{}", s); // Hello, World!
}

+ Operator

fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("World!");

    let s3 = s1 + &s2; // s1 ถูก move!

    // println!("{}", s1); // ❌ Error: s1 was moved
    println!("{}", s2); // ✅ OK
    println!("{}", s3); // Hello, World!
}

หมายเหตุ: + รับ String ซ้าย และ &str ขวา

format! (แนะนำ)

fn main() {
    let s1 = String::from("Hello");
    let s2 = String::from("World");

    // format! ไม่ move อะไรเลย
    let s3 = format!("{}, {}!", s1, s2);

    println!("{}", s1); // ✅ OK
    println!("{}", s2); // ✅ OK
    println!("{}", s3); // Hello, World!
}

UTF-8 และการ Index

String ใน Rust เป็น UTF-8 → ไม่สามารถ index ด้วย s[0] ได้!

fn main() {
    let hello = String::from("สวัสดี");

    // let first = hello[0]; // ❌ Error!

    // ใช้ slicing ได้ (แต่ระวัง!)
    // let first_byte = &hello[0..1]; // 💥 panic! กลาง character
    let first_char = &hello[0..3]; // ✅ "ส" (3 bytes)

    println!("First char: {}", first_char);
}

การวนลูป

fn main() {
    let hello = String::from("สวัสดี");

    // วนตาม characters
    for c in hello.chars() {
        println!("{}", c);
    }

    // วนตาม bytes
    for b in hello.bytes() {
        println!("{}", b);
    }
}

Methods ที่ใช้บ่อย

fn main() {
    let s = String::from("  Hello, World!  ");

    // Length
    println!("len: {}", s.len());              // 18 (bytes)
    println!("chars: {}", s.chars().count()); // 18 (characters)

    // trim
    println!("trimmed: '{}'", s.trim());

    // contains, starts_with, ends_with
    println!("contains 'World': {}", s.contains("World"));

    // replace
    println!("replaced: {}", s.replace("World", "Rust"));

    // split
    for word in s.trim().split(',') {
        println!("word: '{}'", word.trim());
    }

    // to_uppercase, to_lowercase
    println!("upper: {}", s.to_uppercase());
    println!("lower: {}", s.to_lowercase());
}

ตัวอย่างจริง: Parse และ Format

fn main() {
    // Parse string to number
    let num_str = "42";
    let num: i32 = num_str.parse().expect("Not a number");
    println!("Parsed: {}", num);

    // Format number to string
    let formatted = format!("The answer is {}", num);
    println!("{}", formatted);

    // Format with padding
    let padded = format!("{:0>5}", num); // "00042"
    println!("Padded: {}", padded);
}

ลองทำดู! 🎯

  1. สร้าง function ที่นับคำใน string
  2. สร้าง function ที่ reverse string
  3. สร้าง function ที่ตรวจสอบว่าเป็น palindrome หรือไม่

สรุป

แนวคิดตัวอย่าง
CreateString::from("hello")
Appends.push_str("world")
Concatformat!("{}{}", a, b)
Iteratefor c in s.chars()
Slice&s[0..5] (ระวัง UTF-8)

👉 ต่อไป: HashMap

HashMap<K, V>

HashMap เก็บข้อมูลแบบ key-value pairs

สร้าง HashMap

use std::collections::HashMap;

fn main() {
    // สร้าง HashMap ว่าง
    let mut scores: HashMap<String, i32> = HashMap::new();

    // เพิ่มข้อมูล
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    println!("{:?}", scores);
}

หมายเหตุ: ต้อง use std::collections::HashMap;


สร้างจาก Iterator

use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];

    let scores: HashMap<_, _> = teams
        .into_iter()
        .zip(initial_scores.into_iter())
        .collect();

    println!("{:?}", scores);
}

เข้าถึงข้อมูล

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    // get returns Option<&V>
    let team_name = String::from("Blue");

    if let Some(score) = scores.get(&team_name) {
        println!("Blue score: {}", score);
    }

    // get_key_value returns Option<(&K, &V)>
    if let Some((key, value)) = scores.get_key_value(&team_name) {
        println!("{}: {}", key, value);
    }
}

วนลูป

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}

Ownership

use std::collections::HashMap;

fn main() {
    let field_name = String::from("Color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);

    // field_name และ field_value ถูก move แล้ว!
    // println!("{}", field_name); // ❌ Error
}

ใช้ references หรือ clone ถ้าต้องการเก็บ:

use std::collections::HashMap;

fn main() {
    let field_name = String::from("Color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name.clone(), field_value.clone());

    println!("{}: {}", field_name, field_value); // ✅ OK
}

อัปเดตค่า

Overwrite

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25); // overwrite!

    println!("{:?}", scores); // {"Blue": 25}
}

Insert ถ้าไม่มี (entry)

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    // ใส่ค่าเฉพาะเมื่อ key ไม่มี
    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50); // ไม่ทำอะไร

    println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
}

อัปเดตตามค่าเดิม

use std::collections::HashMap;

fn main() {
    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
    // {"hello": 1, "world": 2, "wonderful": 1}
}

Methods ที่ใช้บ่อย

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);

    println!("len: {}", map.len());           // 3
    println!("is_empty: {}", map.is_empty()); // false
    println!("contains a: {}", map.contains_key("a")); // true

    // remove
    let removed = map.remove("a");
    println!("removed: {:?}", removed); // Some(1)

    // keys, values
    for key in map.keys() {
        println!("key: {}", key);
    }

    for value in map.values() {
        println!("value: {}", value);
    }

    // clear
    map.clear();
    println!("after clear: {:?}", map); // {}
}

ตัวอย่างจริง: นับความถี่

use std::collections::HashMap;

fn main() {
    let numbers = vec![1, 2, 3, 2, 1, 3, 3, 3, 4, 5];

    let mut frequency = HashMap::new();

    for num in numbers {
        *frequency.entry(num).or_insert(0) += 1;
    }

    // หาค่าที่มากที่สุด
    let (most_common, count) = frequency
        .iter()
        .max_by_key(|&(_, count)| count)
        .unwrap();

    println!("Most common: {} ({}x)", most_common, count);
    println!("All: {:?}", frequency);
}

ลองทำดู! 🎯

  1. สร้าง phonebook ด้วย HashMap<String, String>
  2. นับความถี่ของ characters ใน string
  3. สร้าง group function ที่จัดกลุ่ม items

สรุปบทที่ 8

Collectionใช้เมื่อ
Vec<T>รายการลำดับ
Stringข้อความ
HashMap<K,V>key-value lookup

👉 ต่อไป: บทที่ 9: Error Handling

บทที่ 9: Error Handling - การจัดการ Error

Rust มีระบบจัดการ error ที่แข็งแกร่ง แบ่งเป็น 2 ประเภท:

ประเภทใช้เมื่อ
panic!Error ร้ายแรง, ไม่สามารถ recover ได้
Result<T, E>Error ที่สามารถจัดการได้

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
panic!เมื่อไหร่ควรใช้
Resultวิธีจัดการ recoverable errors
? operatorการส่งต่อ error แบบกระชับ
Custom Errorsสร้าง error type เอง

เริ่มกันเลย!

👉 panic!

panic! - Unrecoverable Errors

panic! หยุดโปรแกรมทันทีเมื่อเกิด error ที่จัดการไม่ได้

🚨 Error Handling Decision Tree

+-------------------------------------------------------------------+
|                    Error Handling in Rust                         |
+-------------------------------------------------------------------+
|                                                                   |
|                      +--------------+                             |
|                      | Error occurs |                             |
|                      +------+-------+                             |
|                             |                                     |
|           +-----------------+-----------------+                   |
|           |                                   |                   |
|           v                                   v                   |
|   +---------------+                   +---------------+           |
|   | Recoverable?  |                   | Unrecoverable |           |
|   | Can recover   |                   | Cannot recover|           |
|   +-------+-------+                   +-------+-------+           |
|           |                                   |                   |
|           v                                   v                   |
|   +---------------+                   +---------------+           |
|   | Result<T, E>  |                   |   panic!()    |           |
|   |               |                   |               |           |
|   | * File not    |                   | * Bug in code |           |
|   |   found       |                   | * Impossible  |           |
|   | * Parse error |                   |   state       |           |
|   | * Network     |                   | * Contract    |           |
|   |   timeout     |                   |   violation   |           |
|   +---------------+                   +---------------+           |
|                                                                   |
+-------------------------------------------------------------------+

เมื่อไหร่เกิด Panic?

1. เรียก panic! เอง

fn main() {
    panic!("crash and burn");
}

Output:

thread 'main' panicked at 'crash and burn', src/main.rs:2:5

2. Bug ในโค้ด

fn main() {
    let v = vec![1, 2, 3];
    v[99]; // 💥 panic! index out of bounds
}

Backtrace

ดู backtrace เพื่อหาที่มาของ panic:

RUST_BACKTRACE=1 cargo run
```text

```text
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99'
stack backtrace:
   0: std::panicking::begin_panic_handler
   1: core::panicking::panic_bounds_check
   2: <usize as core::slice::SliceIndex<[T]>>::index
   3: playground::main
             at src/main.rs:3:5

เมื่อไหร่ควรใช้ panic!

✅ ควรใช้

  1. Prototyping - ตัวอย่างโค้ด, ทดลอง
fn main() {
    // ยังไม่ได้ implement
    todo!("implement this later");
}
  1. Tests - เมื่อ test fail
#[test]
fn test_something() {
    assert_eq!(1, 2); // panic! ถ้าไม่เท่า
}
  1. Unrecoverable situation - สถานการณ์ที่โปรแกรมต้องหยุด
#![allow(unused)]
fn main() {
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Cannot divide by zero!");
    }
    a / b
}
}
  1. Invalid state - ข้อมูลอยู่ในสถานะที่ไม่ถูกต้อง
#![allow(unused)]
fn main() {
fn process_age(age: i32) {
    if age < 0 || age > 150 {
        panic!("Invalid age: {}", age);
    }
    // ...
}
}

❌ ไม่ควรใช้

  1. Expected failures - เช่น file not found, network error
  2. User input errors - ผู้ใช้พิมพ์ผิด
  3. Recoverable errors - ลอง retry ได้

Rule of thumb: ถ้าผู้เรียกสามารถ handle error ได้ → ใช้ Result

💡 Best Practices: panic! vs Result

+---------------------------------------------------------+
|                  Error Handling Choice                  |
+---------------------------------------------------------+
|  panic!           |  Result<T, E>                       |
+-------------------+-------------------------------------+
|  * Bug in code    |  * File not found                   |
|  * Invalid state  |  * Network error                    |
|  * Tests          |  * User input error                 |
|  * Prototyping    |  * Parse error                      |
|  * Unrecoverable  |  * Recoverable errors               |
+-------------------+-------------------------------------+

unwrap และ expect

unwrap

fn main() {
    let x: Option<i32> = Some(5);
    let value = x.unwrap(); // ✅ 5

    let y: Option<i32> = None;
    // let value = y.unwrap(); // 💥 panic!
}

expect (ดีกว่า unwrap)

fn main() {
    let x: Option<i32> = None;

    // ให้ error message ที่ชัดเจน
    let value = x.expect("x should have a value");
}

Output:

thread 'main' panicked at 'x should have a value', src/main.rs:4:18

unreachable! และ todo!

fn main() {
    let level = 5;

    let description = match level {
        1..=5 => "beginner",
        6..=10 => "intermediate",
        11..=20 => "advanced",
        _ => unreachable!("level should be 1-20"),
    };

    // ยังไม่ได้ implement
    todo!("add more logic here");
}

ตัวอย่างจริง: Assertion

fn set_age(age: u32) {
    assert!(age <= 150, "Age {} is unrealistic", age);
    println!("Age set to {}", age);
}

fn main() {
    set_age(25);  // ✅ OK
    set_age(200); // 💥 panic!
}

ลองทำดู! 🎯

  1. สร้าง function ที่ panic เมื่อได้รับค่าลบ
  2. ใช้ expect แทน unwrap และให้ error message ที่ดี
  3. ลองเปิด RUST_BACKTRACE=1 ดู backtrace

สรุป

Macroใช้เมื่อ
panic!("msg")Error ร้ายแรง
unreachable!()โค้ดที่ไม่ควรถูกเรียก
todo!()ยังไม่ได้ implement
assert!(cond)ตรวจสอบเงื่อนไข

👉 ต่อไป: Result<T, E>

Result<T, E>

Result ใช้จัดการ recoverable errors - error ที่สามารถ handle ได้

นิยาม

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),   // สำเร็จ มีค่า T
    Err(E),  // ล้มเหลว มี error E
}
}

ตัวอย่างพื้นฐาน

use std::fs::File;

fn main() {
    let result = File::open("hello.txt");

    let file = match result {
        Ok(file) => file,
        Err(error) => {
            panic!("Failed to open file: {:?}", error);
        }
    };

    println!("File opened: {:?}", file);
}

จัดการหลายประเภท Error

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let result = File::open("hello.txt");

    let file = match result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => {
                // สร้างไฟล์ใหม่ถ้าไม่มี
                match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => panic!("Cannot create file: {:?}", e),
                }
            }
            other_error => {
                panic!("Cannot open file: {:?}", other_error);
            }
        },
    };

    println!("File: {:?}", file);
}

Shortcuts: unwrap และ expect

unwrap

use std::fs::File;

fn main() {
    // panic ถ้า error
    let file = File::open("hello.txt").unwrap();
}

expect (แนะนำ)

use std::fs::File;

fn main() {
    // panic พร้อม message
    let file = File::open("hello.txt")
        .expect("Failed to open hello.txt");
}

Methods ที่ใช้บ่อย

fn main() {
    let ok_result: Result<i32, &str> = Ok(42);
    let err_result: Result<i32, &str> = Err("error");

    // is_ok, is_err
    println!("ok is_ok: {}", ok_result.is_ok());   // true
    println!("err is_err: {}", err_result.is_err()); // true

    // unwrap_or
    println!("ok: {}", ok_result.unwrap_or(0));   // 42
    println!("err: {}", err_result.unwrap_or(0)); // 0

    // unwrap_or_else
    let value = err_result.unwrap_or_else(|e| {
        println!("Error was: {}", e);
        -1
    });
    println!("value: {}", value); // -1

    // map - transform Ok value
    let doubled = ok_result.map(|x| x * 2);
    println!("doubled: {:?}", doubled); // Ok(84)

    // map_err - transform Err value
    let new_err = err_result.map_err(|e| format!("Error: {}", e));
    println!("new_err: {:?}", new_err); // Err("Error: error")
}

ok() และ err()

แปลง Result เป็น Option:

fn main() {
    let ok_result: Result<i32, &str> = Ok(42);
    let err_result: Result<i32, &str> = Err("error");

    // ok() -> Option<T>
    println!("{:?}", ok_result.ok());   // Some(42)
    println!("{:?}", err_result.ok());  // None

    // err() -> Option<E>
    println!("{:?}", ok_result.err());  // None
    println!("{:?}", err_result.err()); // Some("error")
}

and_then (Chaining)

fn square(x: i32) -> Result<i32, &'static str> {
    if x > 100 {
        Err("Too large to square")
    } else {
        Ok(x * x)
    }
}

fn main() {
    let result = Ok(5)
        .and_then(square)  // Ok(25)
        .and_then(square); // Ok(625)

    println!("{:?}", result); // Ok(625)

    let result2 = Ok(50)
        .and_then(square)   // Ok(2500)
        .and_then(square);  // Err("Too large to square")

    println!("{:?}", result2);
}

ตัวอย่างจริง: Parse Number

fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    let n: i32 = s.parse()?;
    Ok(n * 2)
}

fn main() {
    match parse_and_double("42") {
        Ok(n) => println!("Result: {}", n),
        Err(e) => println!("Error: {}", e),
    }

    match parse_and_double("abc") {
        Ok(n) => println!("Result: {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

ลองทำดู! 🎯

  1. เขียน function divide(a, b) -> Result<f64, String> ที่ return Err เมื่อ b = 0
  2. ใช้ and_then เพื่อ chain หลาย operations
  3. แปลง string เป็น number แล้วบวกเลขสองตัว

สรุป

Methodคำอธิบาย
unwrap()ดึงค่า หรือ panic
expect(msg)ดึงค่า หรือ panic พร้อม message
unwrap_or(default)ดึงค่า หรือใช้ default
map(f)แปลง Ok value
and_then(f)Chain operations
ok()แปลงเป็น Option

👉 ต่อไป: การส่งต่อ Error

การส่งต่อ Error (Propagating Errors)

เมื่อ function เจอ error อาจต้องการส่งต่อให้ caller จัดการ

วิธีดั้งเดิม: match

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let file_result = File::open("username.txt");

    let mut file = match file_result {
        Ok(file) => file,
        Err(e) => return Err(e), // ส่งต่อ error
    };

    let mut username = String::new();

    match file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e), // ส่งต่อ error
    }
}

ยาวไป! 😩


? Operator

? ทำให้โค้ดสั้นลง:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut file = File::open("username.txt")?; // ส่งต่อถ้า Err
    let mut username = String::new();
    file.read_to_string(&mut username)?; // ส่งต่อถ้า Err
    Ok(username)
}

? ทำงานอย่างไร

let file = File::open("file.txt")?;

เทียบเท่ากับ:

let file = match File::open("file.txt") {
    Ok(f) => f,
    Err(e) => return Err(e.into()), // แปลง error type ด้วย
};

Chain ?

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();

    File::open("username.txt")?.read_to_string(&mut username)?;

    Ok(username)
}

หรือสั้นกว่านี้:

use std::fs;

fn read_username_from_file() -> Result<String, String> {
    fs::read_to_string("username.txt")
        .map_err(|e| e.to_string())
}

? กับ Option

ใช้ ? กับ Option ได้ด้วย:

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

fn main() {
    println!("{:?}", last_char_of_first_line("Hello\nWorld")); // Some('o')
    println!("{:?}", last_char_of_first_line(""));             // None
}

? ใน main()

use std::fs::File;
use std::io;

fn main() -> Result<(), io::Error> {
    let _file = File::open("hello.txt")?;
    Ok(())
}

Error Conversion

? เรียก From::from() อัตโนมัติเพื่อแปลง error types:

use std::fs::File;
use std::io::{self, Read};

fn read_number_from_file() -> Result<i32, Box<dyn std::error::Error>> {
    let mut file = File::open("number.txt")?; // io::Error -> Box<dyn Error>
    let mut content = String::new();
    file.read_to_string(&mut content)?;       // io::Error -> Box<dyn Error>
    let number: i32 = content.trim().parse()?; // ParseIntError -> Box<dyn Error>
    Ok(number)
}

ตัวอย่างจริง: API Call

use std::fs;

fn get_config_value(key: &str) -> Result<String, String> {
    let content = fs::read_to_string("config.txt")
        .map_err(|e| format!("Cannot read config: {}", e))?;

    for line in content.lines() {
        let parts: Vec<&str> = line.split('=').collect();
        if parts.len() == 2 && parts[0].trim() == key {
            return Ok(parts[1].trim().to_string());
        }
    }

    Err(format!("Key '{}' not found", key))
}

fn main() {
    match get_config_value("database_url") {
        Ok(value) => println!("Found: {}", value),
        Err(e) => println!("Error: {}", e),
    }
}

Early Return Pattern

fn process_data(data: &str) -> Result<String, &'static str> {
    if data.is_empty() {
        return Err("Data is empty");
    }

    let trimmed = data.trim();

    if trimmed.len() < 3 {
        return Err("Data too short");
    }

    Ok(trimmed.to_uppercase())
}

fn main() {
    println!("{:?}", process_data("  hello  ")); // Ok("HELLO")
    println!("{:?}", process_data(""));           // Err("Data is empty")
    println!("{:?}", process_data("ab"));         // Err("Data too short")
}

ลองทำดู! 🎯

  1. เขียน function ที่อ่านไฟล์และ parse เป็น JSON (ใช้ ?)
  2. Chain หลาย ? ในบรรทัดเดียว
  3. สร้าง function ที่ return Result<T, Box<dyn Error>>

สรุป

แนวคิดตัวอย่าง
? operatorfile.read()?
Chain ?File::open(path)?.read()?
? กับ Optioniter.next()?
main Resultfn main() -> Result<(), E>

👉 ต่อไป: Custom Error Types

Custom Error Types

สร้าง error types เองเพื่อให้มีข้อมูลมากขึ้นและจัดการได้ดีขึ้น

Simple String Error

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 0.0) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

Enum Error Type

#[derive(Debug)]
enum MathError {
    DivisionByZero,
    NegativeSquareRoot,
    Overflow,
}

fn divide(a: f64, b: f64) -> Result<f64, MathError> {
    if b == 0.0 {
        Err(MathError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}

fn sqrt(x: f64) -> Result<f64, MathError> {
    if x < 0.0 {
        Err(MathError::NegativeSquareRoot)
    } else {
        Ok(x.sqrt())
    }
}

fn main() {
    match divide(10.0, 0.0) {
        Ok(result) => println!("Result: {}", result),
        Err(MathError::DivisionByZero) => println!("Cannot divide by zero!"),
        Err(e) => println!("Other error: {:?}", e),
    }
}

Implement Display และ Error

use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum AppError {
    NotFound(String),
    InvalidInput(String),
    DatabaseError(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::NotFound(item) => write!(f, "{} not found", item),
            AppError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
            AppError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
        }
    }
}

impl Error for AppError {}

fn find_user(id: u32) -> Result<String, AppError> {
    if id == 0 {
        Err(AppError::InvalidInput("ID cannot be 0".to_string()))
    } else if id > 100 {
        Err(AppError::NotFound(format!("User {}", id)))
    } else {
        Ok(format!("User_{}", id))
    }
}

fn main() {
    match find_user(0) {
        Ok(user) => println!("Found: {}", user),
        Err(e) => println!("Error: {}", e),
    }

    match find_user(150) {
        Ok(user) => println!("Found: {}", user),
        Err(e) => println!("Error: {}", e),
    }
}

From Trait สำหรับ Error Conversion

use std::io;
use std::num::ParseIntError;
use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum MyError {
    Io(io::Error),
    Parse(ParseIntError),
    Custom(String),
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::Io(e) => write!(f, "IO error: {}", e),
            MyError::Parse(e) => write!(f, "Parse error: {}", e),
            MyError::Custom(msg) => write!(f, "{}", msg),
        }
    }
}

impl Error for MyError {}

// ทำให้ใช้ ? ได้
impl From<io::Error> for MyError {
    fn from(err: io::Error) -> MyError {
        MyError::Io(err)
    }
}

impl From<ParseIntError> for MyError {
    fn from(err: ParseIntError) -> MyError {
        MyError::Parse(err)
    }
}

fn read_number_from_file(path: &str) -> Result<i32, MyError> {
    let content = std::fs::read_to_string(path)?; // io::Error -> MyError
    let number: i32 = content.trim().parse()?;    // ParseIntError -> MyError
    Ok(number)
}

ใช้ thiserror Crate (แนะนำ)

# Cargo.toml
[dependencies]
thiserror = "1.0"
use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("User {0} not found")]
    NotFound(String),

    #[error("Invalid input: {0}")]
    InvalidInput(String),

    #[error("IO error")]
    Io(#[from] std::io::Error),

    #[error("Parse error")]
    Parse(#[from] std::num::ParseIntError),
}

fn process() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("number.txt")?;
    let num: i32 = content.trim().parse()?;
    Ok(num)
}

ใช้ anyhow สำหรับ Applications

# Cargo.toml
[dependencies]
anyhow = "1.0"
use anyhow::{Context, Result};

fn read_config() -> Result<String> {
    let content = std::fs::read_to_string("config.txt")
        .context("Failed to read config file")?;
    Ok(content)
}

fn main() -> Result<()> {
    let config = read_config()?;
    println!("Config: {}", config);
    Ok(())
}

Best Practices

Libraryใช้เมื่อ
Custom enumต้องการ match error types
thiserrorเขียน library
anyhowเขียน application

ลองทำดู! 🎯

  1. สร้าง custom error enum สำหรับ HTTP status codes
  2. Implement From สำหรับ convert ระหว่าง error types
  3. ลองใช้ thiserror หรือ anyhow

สรุปบทที่ 9

แนวคิดใช้เมื่อ
panic!Unrecoverable errors
ResultRecoverable errors
? operatorPropagate errors
Custom errorsต้องการ specific error types

👉 ต่อไป: บทที่ 10: Generics, Traits & Lifetimes

บทที่ 10: Generics, Traits & Lifetimes

บทนี้ครอบคลุม 3 หัวข้อสำคัญที่ทำให้ Rust ทรงพลัง


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Genericsเขียนโค้ดที่ใช้ได้กับหลาย types
Traitsกำหนด behavior ที่ types ต้องมี
Lifetimesระบุอายุของ references

เริ่มกันเลย!

👉 Generics

Generics

Generics ช่วยให้เขียนโค้ดที่ใช้ได้กับหลาย types โดยไม่ต้องเขียนซ้ำ

ปัญหา: Code Duplication

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

โค้ดเหมือนกัน แต่ต่างแค่ type!


Generic Functions

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    println!("Largest number: {}", largest(&numbers));

    let chars = vec!['y', 'm', 'a', 'q'];
    println!("Largest char: {}", largest(&chars));
}

Syntax

fn function_name<T>(param: T) -> T {
    // ...
}

// Multiple type parameters
fn pair<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}

Generic Structs

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };

    // ❌ Error: x และ y ต้องเป็น type เดียวกัน
    // let mixed = Point { x: 5, y: 4.0 };
}

หลาย Type Parameters

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
    let mixed = Point { x: 5, y: 4.0 };  // ✅ OK
}

Generic Methods

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }

    fn y(&self) -> &T {
        &self.y
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };
    println!("x = {}", p.x());
}

Methods สำหรับ Specific Type

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let p = Point { x: 3.0_f32, y: 4.0_f32 };
    println!("Distance: {}", p.distance_from_origin()); // 5.0

    let p2 = Point { x: 3, y: 4 };
    // p2.distance_from_origin(); // ❌ Error: not available for Point<i32>
}

Mix Type Parameters

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
    // Output: p3.x = 5, p3.y = c
}

Generic Enums

Standard library ใช้ generics มากมาย:

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

สร้าง Generic Enum เอง

enum Either<L, R> {
    Left(L),
    Right(R),
}

fn main() {
    let a: Either<i32, String> = Either::Left(42);
    let b: Either<i32, String> = Either::Right(String::from("hello"));
}

Monomorphization

Rust compiles generics เป็น specific types ตอน compile time:

// เราเขียน:
fn id<T>(x: T) -> T { x }

fn main() {
    id(5);
    id("hello");
}

// Compiler สร้าง:
fn id_i32(x: i32) -> i32 { x }
fn id_str(x: &str) -> &str { x }

ผลลัพธ์: zero runtime cost! เหมือนเขียนแยกเอง


ตัวอย่างจริง: Container

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Self {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    fn len(&self) -> usize {
        self.items.len()
    }
}

fn main() {
    let mut stack = Stack::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(item) = stack.pop() {
        println!("{}", item);
    }
    // Output: 3, 2, 1
}

Default Type Parameters

#![allow(unused)]
fn main() {
use std::ops::Add;

// Add trait มี default type parameter
// trait Add<Rhs = Self> { ... }

struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}
}

ลองทำดู! 🎯

  1. สร้าง generic struct Pair<T> ที่มี first และ second
  2. เพิ่ม method swap() ที่สลับ first และ second
  3. สร้าง generic function ที่หาค่า min จาก slice

สรุป

แนวคิดSyntax
Functionfn name<T>(arg: T)
Structstruct Name<T> { field: T }
Enumenum Name<T> { Variant(T) }
implimpl<T> Name<T> { ... }
Specific implimpl Name<f32> { ... }
Multiple<T, U, V>

ข้อดี

  • ✅ ไม่ต้องเขียนโค้ดซ้ำ
  • ✅ Type-safe
  • ✅ Zero runtime cost (monomorphization)

👉 ต่อไป: Traits

Traits

Traits กำหนด behavior ที่ types ต้องมี คล้าย interfaces ในภาษาอื่น

นิยาม Trait

trait Summary {
    fn summarize(&self) -> String;
}

Implement Trait

trait Summary {
    fn summarize(&self) -> String;
}

struct Article {
    title: String,
    author: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = Article {
        title: String::from("Rust is great"),
        author: String::from("Alice"),
        content: String::from("..."),
    };

    println!("{}", article.summarize());
}

Default Implementation

trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

struct Article {
    title: String,
}

impl Summary for Article {} // ใช้ default

fn main() {
    let article = Article { title: String::from("News") };
    println!("{}", article.summarize()); // (Read more...)
}

Traits as Parameters

fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// หรือใช้ trait bound syntax
fn notify2<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

Derive Attribute

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1.clone();

    println!("{:?}", p1);
    println!("Equal: {}", p1 == p2);
}

Common derives: Debug, Clone, Copy, PartialEq, Eq, Hash, Default


สรุป

แนวคิดSyntax
Definetrait Name { fn method(&self); }
Implementimpl Trait for Type { ... }
Parameterfn func(item: &impl Trait)
Derive#[derive(Debug, Clone)]

👉 ต่อไป: Trait Bounds

Trait Bounds

Trait Bounds จำกัดว่า generic type ต้องมี traits อะไรบ้าง

Basic Syntax

fn print_summary<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// หรือ impl syntax
fn print_summary2(item: &impl Summary) {
    println!("{}", item.summarize());
}

Multiple Trait Bounds

fn notify<T: Summary + Display>(item: &T) {
    println!("Summary: {}", item.summarize());
    println!("Display: {}", item);
}

// impl syntax
fn notify2(item: &(impl Summary + Display)) {
    // ...
}

Where Clause

อ่านง่ายกว่าเมื่อมีหลาย bounds:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
    0
}

Return Trait

fn get_summarizable() -> impl Summary {
    Tweet {
        username: String::from("user"),
        content: String::from("hello"),
    }
}

หมายเหตุ: สามารถ return ได้แค่ type เดียว


Conditionally Implement Methods

#![allow(unused)]
fn main() {
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

// Methods only for types with Display + PartialOrd
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("Larger: {}", self.x);
        } else {
            println!("Larger: {}", self.y);
        }
    }
}
}

สรุป

Syntaxตัวอย่าง
Single boundT: Display
MultipleT: Display + Clone
Wherewhere T: Display
Return-> impl Trait

👉 ต่อไป: Lifetimes

Lifetimes

Lifetimes บอก compiler ว่า references มีอายุนานเท่าไหร่ ป้องกัน dangling references

⚠️ คำเตือน: Lifetime เป็นหัวข้อที่ยากที่สุดใน Rust

  • ❌ อย่าใช้ 'static เพื่อแก้ทุก error
  • ❌ อย่าเดา lifetime แบบสุ่ม
  • ✅ เข้าใจปัญหาก่อนแก้
  • ✅ ใช้ owned types (String แทน &str) เมื่อสงสัย

📋 Lifetime Cheatsheet

สถานการณ์ต้องใส่ Lifetime?ตัวอย่าง
Return reference จาก function✅ ใช่fn foo<'a>(x: &'a str) -> &'a str
1 input reference❌ ไม่ต้อง (elision)fn foo(x: &str) -> &str
Method with &self❌ ไม่ต้อง (elision)fn bar(&self) -> &str
Struct เก็บ reference✅ ใช่struct Foo<'a> { x: &'a str }
Static string❌ ไม่ต้องlet s: &'static str = "hello"

🧠 Mental Model

Lifetime Scopes Diagram

🔧 Common Patterns

1. Input → Output (ใช้ lifetime เดียวกัน)
   fn get_first<'a>(list: &'a [i32]) -> &'a i32

2. Struct เก็บ reference
   struct Parser<'a> { input: &'a str }

3. Multiple lifetimes (แยกคนละช่วง)
   fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &'a str

4. 'static (อยู่ตลอดโปรแกรม)
   const MSG: &'static str = "Hello";

ปัญหา: Dangling Reference

fn main() {
    let r;                // declare r
    {
        let x = 5;
        r = &x;           // ❌ x จะหายเมื่อออกจาก scope
    }
    // println!("{}", r); // Error! r points to invalid memory
}
+---------------------------------------------------------+
|                 Dangling Reference                      |
+---------------------------------------------------------+
|                                                         |
|  let r; ---------+                                      |
|  {               |                                      |
|    let x = 5; <--+ r points to x                        |
|    r = &x;                                              |
|  } <-- x dropped here!                                  |
|                                                         |
|  r -> invalid memory                                    |
|                                                         |
+---------------------------------------------------------+

Rust compiler จะ reject โค้ดนี้เพราะ x ไม่อยู่แล้วเมื่อใช้ r


Lifetime Syntax

&i32        // reference
&'a i32     // reference with explicit lifetime 'a
&'a mut i32 // mutable reference with lifetime 'a

'a (อ่านว่า “tick a”) คือ lifetime parameter บอกว่า reference มีอายุเท่าไหร่


Lifetime ใน Functions

ปัญหา: Compiler ไม่รู้ว่า return อะไร

#![allow(unused)]
fn main() {
// ❌ Error: missing lifetime specifier
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}

Compiler ไม่รู้ว่า return value จะอยู่นานเท่า x หรือ y

ทางแก้: Lifetime Annotations

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 string2 = String::from("xyz");

    let result = longest(&string1, &string2);
    println!("Longest: {}", result);
}

ความหมาย: Return value จะมีอายุเท่ากับ lifetime ที่สั้นกว่าระหว่าง x และ y


Lifetime กับ Scope

fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(&string1, &string2);
        println!("Longest: {}", result); // ✅ OK
    }

    // println!("{}", result); // ❌ result ไม่มีแล้ว
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Lifetime Elision Rules

Compiler มีกฎ 3 ข้อที่เดา lifetime ให้อัตโนมัติ:

Rule 1: Input Lifetimes

แต่ละ reference parameter ได้ lifetime ของตัวเอง:

fn foo(x: &i32)                    // -> fn foo<'a>(x: &'a i32)
fn foo(x: &i32, y: &i32)           // -> fn foo<'a, 'b>(x: &'a i32, y: &'b i32)

Rule 2: Single Input → Output

ถ้ามี input lifetime เดียว ใช้กับ output ทั้งหมด:

fn foo(x: &i32) -> &i32            // -> fn foo<'a>(x: &'a i32) -> &'a i32

Rule 3: Methods with &self

ถ้ามี &self หรือ &mut self ใช้ lifetime ของ self กับ output:

impl Foo {
    fn bar(&self, x: &str) -> &str // -> fn bar<'a, 'b>(&'a self, x: &'b str) -> &'a str
}

ถ้ากฎทั้งหมดไม่เพียงพอ → Compiler error → ต้องใส่ lifetime เอง


ตัวอย่าง: เมื่อต้องใส่ Lifetime

#![allow(unused)]
fn main() {
// ✅ Elision works - Rule 2
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

// ❌ Elision fails - must annotate
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
}

Lifetime ใน Structs

Struct ที่มี references ต้องมี lifetime annotation:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();

    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };

    println!("{}", excerpt.part);
}
// excerpt ต้องไม่อยู่นานกว่า novel

Methods on Structs with Lifetimes

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }

    // Rule 3 applies: return lifetime = self lifetime
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention: {}", announcement);
        self.part
    }
}

’static Lifetime

'static หมายถึง reference อยู่ได้ตลอดโปรแกรม:

#![allow(unused)]
fn main() {
let s: &'static str = "I have a static lifetime.";
}

String literals ทั้งหมดมี 'static lifetime เพราะเก็บใน binary

ระวัง!

อย่าใช้ 'static เพื่อ “แก้” lifetime errors โดยไม่เข้าใจ:

#![allow(unused)]
fn main() {
// ❌ Bad: ใช้ 'static แบบผิดๆ
fn get_str() -> &'static str {
    let s = String::from("hello");
    &s  // ❌ s จะถูก drop!
}

// ✅ Good: return owned String
fn get_str_good() -> String {
    String::from("hello")
}
}

Multiple Lifetimes

บางครั้งต้องใช้หลาย lifetimes:

#![allow(unused)]
fn main() {
fn longest_with_announcement<'a, 'b>(
    x: &'a str,
    y: &'a str,
    ann: &'b str,
) -> &'a str {
    println!("Announcement: {}", ann);
    if x.len() > y.len() { x } else { y }
}
}

Lifetime Bounds

ใช้กับ generics:

#![allow(unused)]
fn main() {
fn longest_with_trait<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
    T: std::fmt::Display,
{
    println!("Announcement: {}", ann);
    if x.len() > y.len() { x } else { y }
}
}

ลองทำดู! 🎯

  1. เขียน struct ที่มี lifetime annotation
  2. เขียน function ที่ต้องใส่ lifetime
  3. ลองตัด lifetime ออกและดู compiler error

สรุป

แนวคิดตัวอย่าง
Annotation&'a str
Functionfn foo<'a>(x: &'a str) -> &'a str
Structstruct Foo<'a> { x: &'a str }
’staticอายุตลอดโปรแกรม
ElisionCompiler เดาให้

กฎ Elision

  1. แต่ละ input ได้ lifetime ตัวเอง
  2. input เดียว → ใช้กับ output
  3. &self → output ใช้ lifetime ของ self

👉 ต่อไป: บทที่ 11: Modules & Packages

บทที่ 11: Modules & Packages

จัดระเบียบโค้ดเป็น modules และ packages


สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Packages & Cratesหน่วยใหญ่ของโค้ด
Modulesจัดกลุ่มโค้ด
Pathsเข้าถึง items
แยกไฟล์โครงสร้างโปรเจกต์ใหญ่

👉 Packages & Crates

Packages & Crates

Crate

Crate คือหน่วยการ compile ที่เล็กที่สุด มี 2 แบบ:

  • Binary crate - โปรแกรมที่รันได้ (มี main)
  • Library crate - โค้ดที่ใช้ร่วมกัน (ไม่มี main)

📦 Package Structure Visualization

+-------------------------------------------------------------------+
|                    Rust Package Hierarchy                         |
+-------------------------------------------------------------------+
|                                                                   |
|   Package (Cargo.toml)                                            |
|   +-- Binary Crate (src/main.rs)      <--- executable             |
|   |   +-- mod auth                                                |
|   |   +-- mod database                                            |
|   |   +-- mod routes                                              |
|   |                                                               |
|   +-- Library Crate (src/lib.rs)      <--- shared code            |
|       +-- pub mod models                                          |
|       +-- pub mod utils                                           |
|       +-- pub mod errors                                          |
|                                                                   |
+-------------------------------------------------------------------+
|   Dependencies (Cargo.toml)                                       |
|   +-- serde = "1.0"                   <--- External crates        |
|   +-- tokio = { version = "1", features = ["full"] }              |
|   +-- rand = "0.8"                                                |
|                                                                   |
+-------------------------------------------------------------------+

Package

Package = collection ของ crates พร้อม Cargo.toml

my-package/
├── Cargo.toml
├── src/
│   ├── main.rs      # binary crate root
│   └── lib.rs       # library crate root (optional)
└── src/bin/         # additional binaries
    └── another.rs

สร้าง Package

# Binary package
cargo new my-app

# Library package
cargo new my-lib --lib

Cargo.toml

[package]
name = "my-app"
version = "0.1.0"
edition = "2024"

[dependencies]
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

👉 ต่อไป: Modules

Modules

Modules จัดกลุ่มโค้ดและควบคุม privacy เป็นพื้นฐานของการจัดโครงสร้างโปรเจกต์ใน Rust

Module Tree

โปรเจกต์ Rust มีโครงสร้างเป็น tree:

Module Tree Diagram


นิยาม Module

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        fn seat_at_table() {} // private
    }

    mod serving {
        fn take_order() {}
        fn serve_order() {}
    }
}

fn main() {
    // เรียกใช้ public function
    front_of_house::hosting::add_to_waitlist();
}

pub Keyword

Default: ทุกอย่างเป็น private

mod my_module {
    pub fn public_function() {}  // ✅ เข้าถึงได้จากนอก
    fn private_function() {}     // ❌ private

    pub struct PublicStruct {
        pub name: String,        // ✅ field public
        age: u32,                // ❌ field private
    }

    pub enum PublicEnum {
        Variant1,  // ✅ variants ของ pub enum เป็น public
        Variant2,
    }
}

Visibility Modifiers

ModifierVisibility
(default)Private ใน module เดียว
pubPublic ทุกที่
pub(crate)Public ใน crate เดียว
pub(super)Public ใน parent module
pub(in path)Public ใน path ที่ระบุ
mod outer {
    pub(crate) fn crate_only() {}

    pub mod inner {
        pub(super) fn parent_only() {}

        pub(in crate::outer) fn outer_only() {}
    }
}

fn main() {
    outer::crate_only();      // ✅ OK - same crate
    // outer::inner::parent_only(); // ❌ Error - only visible to outer
}

ใช้ use

Basic use

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use front_of_house::hosting;

fn main() {
    hosting::add_to_waitlist();
}

Idiomatic use

// ✅ Good: use parent module สำหรับ functions
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("key", "value");
}

// ✅ Good: use full path สำหรับ structs/enums
use std::collections::HashMap;
use std::io::Result;

use กับ as (alias)

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    Ok(())
}

fn function2() -> IoResult<()> {
    Ok(())
}

Re-exporting with pub use

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// Re-export ให้ผู้ใช้ library เข้าถึงได้ง่าย
pub use crate::front_of_house::hosting;

// ผู้ใช้ library เรียกได้แค่:
// use my_crate::hosting;
// hosting::add_to_waitlist();

Nested Paths

// แทนที่จะเขียน:
use std::cmp::Ordering;
use std::io;

// เขียนแบบนี้:
use std::{cmp::Ordering, io};

// หรือ:
use std::io::{self, Write};
// = use std::io และ use std::io::Write

Glob Operator

// นำเข้าทุกอย่างที่เป็น public
use std::collections::*;

fn main() {
    let mut map = HashMap::new();
    let mut set = HashSet::new();
}

คำเตือน: ใช้ * ระวัง เพราะไม่ชัดเจนว่า names มาจากไหน


ตัวอย่างจริง: Library Structure

// lib.rs
mod authentication;
mod database;
mod handlers;

pub use authentication::User;
pub use database::Connection;

pub mod api {
    pub use crate::handlers::*;
}

ผู้ใช้ library:

use my_lib::User;
use my_lib::Connection;
use my_lib::api::get_users;

Privacy Rules

  1. Parent can’t see private children
  2. Children can see private ancestors
  3. Siblings can see each other
mod parent {
    fn parent_private() {}
    pub fn parent_public() {}

    mod child {
        fn child_func() {
            super::parent_private(); // ✅ Child can access parent's private
            super::parent_public();  // ✅
        }
    }

    mod sibling {
        fn call_child() {
            // super::child::child_func(); // ❌ Can't access sibling's private
        }
    }
}

ลองทำดู! 🎯

  1. สร้าง module tree 3 ระดับ
  2. ใช้ pub(crate) และ pub(super)
  3. Re-export ด้วย pub use

สรุป

แนวคิดSyntax
Define modulemod name { }
Publicpub fn, pub struct
Crate-onlypub(crate) fn
Parent-onlypub(super) fn
Useuse path::to::item;
Aliasuse path as name;
Re-exportpub use path;
Nesteduse std::{io, fmt};
Globuse module::*;

👉 ต่อไป: Paths

Paths

เข้าถึง items ใน module tree ด้วย paths

2 รูปแบบของ Path

Typeเริ่มจากSyntax
Absolutecrate rootcrate::module::item
Relativecurrent modulemodule::item

Absolute Path

เริ่มจาก crate root ด้วย crate:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Adding to waitlist");
        }
    }
}

fn main() {
    // Absolute path - ชัดเจน ไม่กำกวม
    crate::front_of_house::hosting::add_to_waitlist();
}

Relative Path

เริ่มจาก module ปัจจุบัน:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

fn main() {
    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

super Keyword

super = parent module (เหมือน .. ใน filesystem)

mod parent {
    fn parent_function() {
        println!("In parent");
    }

    pub mod child {
        pub fn call_parent() {
            // super ชี้ไปที่ parent module
            super::parent_function();
        }

        pub fn call_sibling() {
            // เรียก sibling module
            super::sibling::sibling_function();
        }
    }

    pub mod sibling {
        pub fn sibling_function() {
            println!("In sibling");
        }
    }
}

fn main() {
    parent::child::call_parent();
    parent::child::call_sibling();
}

self Keyword

self = current module

mod my_module {
    pub fn function_a() {
        println!("Function A");
    }

    pub fn function_b() {
        // ใช้ self เรียก function ใน module เดียวกัน
        self::function_a();

        // หรือเรียกตรงๆ ก็ได้
        function_a();
    }
}

ใช้ self กับ use

mod outer {
    pub mod inner {
        pub fn func() {}
    }

    pub fn use_inner() {
        // use self เพื่อนำเข้าจาก current module
        use self::inner::func;
        func();
    }
}

Path ใน use Statement

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        pub fn seat_at_table() {}
    }
}

// นำเข้า module
use crate::front_of_house::hosting;

// หรือนำเข้า function โดยตรง
use crate::front_of_house::hosting::add_to_waitlist;

fn main() {
    // เรียกผ่าน module
    hosting::add_to_waitlist();

    // หรือเรียกตรง
    add_to_waitlist();
}

เลือกใช้ Absolute vs Relative?

Situationใช้
Code อาจย้ายAbsolute
อยู่ใกล้กันRelative
Re-exportAbsolute
Libraryแบบไหนก็ได้
// ✅ Good: ถ้าย้าย main ไปที่อื่น ไม่ต้องแก้
crate::utils::helper();

// ⚠️ Might break: ถ้าย้าย function นี้ไปที่อื่น
utils::helper();

ตัวอย่างจริง

mod database {
    pub mod connection {
        pub fn connect() {
            println!("Connecting...");
        }
    }

    pub mod queries {
        use super::connection;  // parent's sibling

        pub fn execute() {
            connection::connect();
            println!("Executing query...");
        }
    }
}

mod handlers {
    use crate::database;  // absolute path

    pub fn handle_request() {
        database::queries::execute();
    }
}

fn main() {
    handlers::handle_request();
}

ลองทำดู! 🎯

  1. สร้าง nested modules และใช้ super
  2. ใช้ self ใน use statement
  3. เปรียบเทียบ absolute และ relative paths

สรุป

Keywordความหมาย
crateRoot ของ crate ปัจจุบัน
superParent module
selfCurrent module
::Path separator

Path Examples

crate::module::item        // absolute
module::item               // relative
self::item                 // current module
super::item                // parent module
super::super::item         // grandparent

👉 ต่อไป: แยกไฟล์

แยกไฟล์

จัดโครงสร้างโปรเจกต์ใหญ่ด้วยการแยก modules เป็นไฟล์

2 รูปแบบโครงสร้าง

รูปแบบ 1: ไฟล์เดียว

src/
├── main.rs
└── my_module.rs   ← mod my_module; จะหาที่นี่

รูปแบบ 2: โฟลเดอร์ + mod.rs

src/
├── main.rs
└── my_module/
    ├── mod.rs      ← mod my_module; จะหาที่นี่
    └── submodule.rs

ตัวอย่าง: โปรเจกต์จริง

โครงสร้าง

my_project/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── lib.rs
    ├── config.rs
    ├── database/
    │   ├── mod.rs
    │   ├── connection.rs
    │   └── queries.rs
    └── handlers/
        ├── mod.rs
        ├── users.rs
        └── posts.rs

src/lib.rs

// ประกาศ modules
pub mod config;
pub mod database;
pub mod handlers;

src/config.rs

pub struct Config {
    pub database_url: String,
    pub port: u16,
}

impl Config {
    pub fn new() -> Self {
        Config {
            database_url: String::from("postgres://localhost/mydb"),
            port: 3000,
        }
    }
}

src/database/mod.rs

// ประกาศ submodules
pub mod connection;
pub mod queries;

// Re-export items ที่ใช้บ่อย
pub use connection::DatabaseConnection;
pub use queries::Query;

src/database/connection.rs

pub struct DatabaseConnection {
    url: String,
    connected: bool,
}

impl DatabaseConnection {
    pub fn new(url: &str) -> Self {
        DatabaseConnection {
            url: url.to_string(),
            connected: false,
        }
    }

    pub fn connect(&mut self) {
        self.connected = true;
        println!("Connected to {}", self.url);
    }
}

src/database/queries.rs

use super::DatabaseConnection;

pub struct Query {
    sql: String,
}

impl Query {
    pub fn new(sql: &str) -> Self {
        Query { sql: sql.to_string() }
    }

    pub fn execute(&self, _conn: &DatabaseConnection) {
        println!("Executing: {}", self.sql);
    }
}

src/handlers/mod.rs

pub mod users;
pub mod posts;

src/main.rs

use my_project::{config::Config, database::DatabaseConnection};

fn main() {
    let config = Config::new();
    let mut db = DatabaseConnection::new(&config.database_url);
    db.connect();

    println!("Server starting on port {}", config.port);
}

Module Discovery Rules

Rust หา module ตามลำดับนี้:

  1. Inline: mod name { ... } ใน file เดียวกัน
  2. File: src/name.rs
  3. Directory: src/name/mod.rs
// src/lib.rs
mod foo;        // หา src/foo.rs หรือ src/foo/mod.rs
mod bar;        // หา src/bar.rs หรือ src/bar/mod.rs

mod inline {    // inline module
    pub fn stuff() {}
}

2018+ Edition Style (แนะนำ)

แทนที่จะใช้ mod.rs ใช้ชื่อ folder:

src/
├── lib.rs
├── database.rs      ← แทน database/mod.rs
└── database/
    ├── connection.rs
    └── queries.rs
```text

```rust,ignore
// src/database.rs
pub mod connection;
pub mod queries;

Private vs Public Files

// src/lib.rs
mod private_module;      // private - ใช้ได้ใน crate นี้เท่านั้น
pub mod public_module;   // public - user ของ crate ใช้ได้

Workspace

สำหรับโปรเจกต์ใหญ่มาก:

my_workspace/
├── Cargo.toml           ← [workspace]
├── crates/
│   ├── core/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   ├── api/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   └── cli/
│       ├── Cargo.toml
│       └── src/main.rs
```text

```toml,ignore
# Cargo.toml (workspace root)
[workspace]
members = ["crates/*"]

ลองทำดู! 🎯

  1. สร้างโปรเจกต์ที่มี 2-3 modules
  2. แยก modules เป็นไฟล์
  3. ใช้ pub use re-export

สรุปบทที่ 11

PatternLocation
mod foosrc/foo.rs หรือ src/foo/mod.rs
mod foo::barsrc/foo/bar.rs
Inlinemod foo { ... }

Best Practices

  1. ใช้ 2018 style (ไม่ใช้ mod.rs)
  2. pub use re-export items ที่สำคัญ
  3. ใช้ workspace สำหรับโปรเจกต์ใหญ่

👉 ต่อไป: บทที่ 12: Testing

บทที่ 12: Testing

Rust มี testing framework ในตัว

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Unit Testsทดสอบแต่ละ function
Integration Testsทดสอบ modules ร่วมกัน
Test Organizationจัดระเบียบ tests

👉 Unit Tests

Unit Tests

Rust มี testing framework ในตัว ไม่ต้องติดตั้งเพิ่ม

🧪 Testing Pyramid in Rust

+-------------------------------------------------------------------+
|                    Testing Pyramid                                |
+-------------------------------------------------------------------+
|                                                                   |
|                           /\                                      |
|                          /  \                                     |
|                         / E2E\      <--- Slow, Expensive          |
|                        /------\                                   |
|                       /        \                                  |
|                      /Integration\ <--- tests/ directory          |
|                     /--------------\                              |
|                    /                \                             |
|                   /   Unit Tests     \ <--- Fast, Many            |
|                  /--------------------\     #[cfg(test)]          |
|                                                                   |
+-------------------------------------------------------------------+
|   Rust Test Commands:                                             |
|   * cargo test              --> Run all tests                     |
|   * cargo test --lib        --> Unit tests only                   |
|   * cargo test --test '*'   --> Integration tests                 |
|   * cargo test --doc        --> Doc tests                         |
+-------------------------------------------------------------------+

โครงสร้างพื้นฐาน

fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;  // นำเข้า items จาก parent module

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-1, 1), 0);
    }
}

รัน Tests

# รัน tests ทั้งหมด
cargo test

# รัน test เฉพาะชื่อ
cargo test test_add

# รัน tests ที่มีคำนี้
cargo test add

# รัน tests ใน module
cargo test tests::

Assertions

assert!

#[test]
fn test_assert() {
    assert!(true);
    assert!(1 + 1 == 2);

    // Custom error message
    assert!(4 > 2, "4 should be greater than 2");

    // กับ format arguments
    let x = 5;
    assert!(x > 0, "x should be positive, got {}", x);
}

assert_eq! และ assert_ne!

#[test]
fn test_equality() {
    assert_eq!(4, 2 + 2);
    assert_ne!(4, 5);

    // Custom message
    let result = 10;
    assert_eq!(result, 10, "Expected 10, got {}", result);
}

debug_assert!

เฉพาะ debug builds:

fn expensive_check(x: i32) -> bool {
    // expensive computation
    x > 0
}

fn process(x: i32) {
    debug_assert!(expensive_check(x), "x must be positive");
    // ใน release build, debug_assert! ถูกลบออก
}

ทดสอบ Panic

#[should_panic]

pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Cannot divide by zero!");
    }
    a / b
}

#[test]
#[should_panic]
fn test_divide_by_zero() {
    divide(10, 0);
}

// ระบุ expected message
#[test]
#[should_panic(expected = "Cannot divide by zero")]
fn test_divide_by_zero_message() {
    divide(10, 0);
}

ใช้ Result ใน Tests

#[test]
fn test_with_result() -> Result<(), String> {
    if 2 + 2 == 4 {
        Ok(())
    } else {
        Err(String::from("Math is broken"))
    }
}

// ใช้กับ ? operator
#[test]
fn test_file_operations() -> Result<(), std::io::Error> {
    let content = std::fs::read_to_string("test.txt")?;
    assert!(content.contains("expected"));
    Ok(())
}

Test Output Control

แสดง println! output

# ปกติ output จะถูกซ่อน
cargo test

# แสดง output
cargo test -- --show-output

# หรือ
cargo test -- --nocapture

ตัวอย่าง

#[test]
fn test_with_output() {
    println!("Setting up test...");
    let result = 2 + 2;
    println!("Result: {}", result);
    assert_eq!(result, 4);
    println!("Test passed!");
}

Control Test Execution

รันแบบ Single Thread

# ปกติ tests รันแบบ parallel
cargo test

# รันแบบ sequential
cargo test -- --test-threads=1

Ignore Tests

#[test]
#[ignore]
fn expensive_test() {
    // takes a long time
    std::thread::sleep(std::time::Duration::from_secs(60));
}

// รัน ignored tests
// cargo test -- --ignored

// รันทุก tests รวม ignored
// cargo test -- --include-ignored

Test Private Functions

ใน Rust ทดสอบ private functions ได้:

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_internal() {
        // ✅ เข้าถึง private function ได้
        assert_eq!(internal_adder(2, 2), 4);
    }
}

Doc Tests

ทดสอบ code ใน documentation:

#![allow(unused)]
fn main() {
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```text
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
}
# Doc tests รันกับ cargo test
cargo test

# รันเฉพาะ doc tests
cargo test --doc

Test Filtering

# รัน tests ที่ชื่อขึ้นต้นด้วย 'test_'
cargo test test_

# รัน tests ใน specific module
cargo test tests::unit::

# รันเฉพาะ test เดียว
cargo test tests::test_add -- --exact

Test Organization Best Practices

// src/lib.rs

pub fn public_function() -> i32 {
    private_helper() + 1
}

fn private_helper() -> i32 {
    41
}

// Unit tests อยู่ในไฟล์เดียวกัน
#[cfg(test)]
mod tests {
    use super::*;

    mod public_api_tests {
        use super::*;

        #[test]
        fn test_public_function() {
            assert_eq!(public_function(), 42);
        }
    }

    mod internal_tests {
        use super::*;

        #[test]
        fn test_private_helper() {
            assert_eq!(private_helper(), 41);
        }
    }
}

ลองทำดู! 🎯

  1. เขียน tests สำหรับ function ที่ควร panic
  2. ใช้ #[ignore] และลองรัน cargo test -- --ignored
  3. เขียน doc test ใน function documentation

สรุป

Attribute/Macroคำอธิบาย
#[test]Mark as test
#[cfg(test)]Compile only for test
#[should_panic]Expect panic
#[ignore]Skip test
assert!Check condition
assert_eq!Check equality
assert_ne!Check inequality

Cargo Test Flags

Flagคำอธิบาย
--show-outputแสดง println!
--test-threads=1รันแบบ sequential
--ignoredรัน ignored tests
--exactMatch exact name

👉 ต่อไป: Integration Tests

Integration Tests

Integration tests ทดสอบ library จากมุมมองของ user ภายนอก

Unit Tests vs Integration Tests

AspectUnit TestsIntegration Tests
Locationsrc/ (ใน module)tests/ (แยกต่างหาก)
AccessPrivate items ได้Public API เท่านั้น
PurposeTest ทีละ unitTest การทำงานรวม
Speedเร็วกว่าช้ากว่า

โครงสร้างโปรเจกต์

my_library/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/
    ├── common/
    │   └── mod.rs        ← shared test utilities
    ├── integration_test.rs
    └── another_test.rs

สร้าง Integration Test

src/lib.rs

#![allow(unused)]
fn main() {
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

pub struct Calculator {
    value: i32,
}

impl Calculator {
    pub fn new() -> Self {
        Calculator { value: 0 }
    }

    pub fn add(&mut self, n: i32) {
        self.value += n;
    }

    pub fn result(&self) -> i32 {
        self.value
    }
}
}

tests/integration_test.rs

use my_library::{add, multiply, Calculator};

#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);
    assert_eq!(add(-1, 1), 0);
    assert_eq!(add(0, 0), 0);
}

#[test]
fn test_multiply() {
    assert_eq!(multiply(2, 3), 6);
    assert_eq!(multiply(-2, 3), -6);
    assert_eq!(multiply(0, 100), 0);
}

#[test]
fn test_calculator_workflow() {
    let mut calc = Calculator::new();

    calc.add(5);
    calc.add(10);
    calc.add(-3);

    assert_eq!(calc.result(), 12);
}

Shared Test Code

tests/common/mod.rs

use my_library::Calculator;

pub fn setup_calculator_with_value(value: i32) -> Calculator {
    let mut calc = Calculator::new();
    calc.add(value);
    calc
}

pub fn assert_close(a: f64, b: f64, epsilon: f64) {
    assert!((a - b).abs() < epsilon,
            "Expected {} to be close to {}", a, b);
}

pub struct TestContext {
    pub name: String,
    pub created_at: std::time::Instant,
}

impl TestContext {
    pub fn new(name: &str) -> Self {
        println!("Setting up test: {}", name);
        TestContext {
            name: name.to_string(),
            created_at: std::time::Instant::now(),
        }
    }
}

impl Drop for TestContext {
    fn drop(&mut self) {
        println!("Tearing down test: {} (took {:?})",
                 self.name,
                 self.created_at.elapsed());
    }
}

ใช้ Shared Code

// tests/integration_test.rs
mod common;

use my_library::Calculator;

#[test]
fn test_with_setup() {
    let _ctx = common::TestContext::new("calculator_test");

    let calc = common::setup_calculator_with_value(100);
    assert_eq!(calc.result(), 100);
}

รัน Integration Tests

# รันทุก tests (unit + integration)
cargo test

# รันเฉพาะ integration tests
cargo test --test integration_test

# รันเฉพาะ test เดียว
cargo test --test integration_test test_add

# รันทุก integration test files
cargo test --test '*'

Binary Crates

สำคัญ: Integration tests ใช้ได้กับ library crates เท่านั้น

ถ้ามีแค่ main.rs (binary crate):

// src/main.rs
fn main() {
    let result = add(2, 3);
    println!("{}", result);
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}

ทางแก้: แยก logic ไปไว้ใน lib.rs:

// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// src/main.rs
use my_crate::add;

fn main() {
    println!("{}", add(2, 3));
}

หลาย Test Files

// tests/math_tests.rs
use my_library::{add, multiply};

#[test]
fn test_math_operations() {
    assert_eq!(add(multiply(2, 3), 4), 10);
}

// tests/calculator_tests.rs
use my_library::Calculator;

#[test]
fn test_calculator_chain() {
    let mut calc = Calculator::new();
    calc.add(1);
    calc.add(2);
    calc.add(3);
    assert_eq!(calc.result(), 6);
}

Async Integration Tests

// tests/async_test.rs

#[tokio::test]
async fn test_async_function() {
    let result = my_library::async_fetch().await;
    assert!(result.is_ok());
}

ลองทำดู! 🎯

  1. สร้าง tests/ folder และ integration test
  2. สร้าง shared utilities ใน tests/common/mod.rs
  3. รันเฉพาะ integration tests ด้วย --test

สรุป

CommandDescription
cargo testRun all tests
cargo test --test NAMERun specific test file
cargo test --libRun only unit tests
cargo test --docRun only doc tests

File Structure

tests/
├── common/mod.rs     ← shared code (NOT a test)
├── test_a.rs         ← first test file
└── test_b.rs         ← second test file

👉 ต่อไป: จัดระเบียบ Tests

จัดระเบียบ Tests

วิธีจัดระเบียบ tests ให้ maintain ง่าย

#[cfg(test)]

Compile test code เฉพาะ เมื่อรัน cargo test:

#![allow(unused)]
fn main() {
// Production code
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Test code - ไม่รวมใน production build
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}
}

ทำไมต้องใช้?

  • ลดขนาด binary
  • ไม่ compile test dependencies
  • code ที่ใช้แค่ test ไม่เข้าไปใน production

#[ignore] - ข้าม Tests

#[test]
fn fast_test() {
    assert!(true);
}

#[test]
#[ignore = "takes too long"]
fn slow_test() {
    std::thread::sleep(std::time::Duration::from_secs(10));
    assert!(true);
}

#[test]
#[ignore = "requires database"]
fn db_test() {
    // needs database connection
}

รัน Ignored Tests

# รันเฉพาะ tests ปกติ (ข้าม ignored)
cargo test

# รันเฉพาะ ignored tests
cargo test -- --ignored

# รันทั้งหมด (รวม ignored)
cargo test -- --include-ignored

Test Attributes รวม

#[test]
fn normal_test() {}

#[test]
#[ignore]
fn ignored_test() {}

#[test]
#[should_panic]
fn expected_panic() {
    panic!("This should happen");
}

#[test]
#[should_panic(expected = "specific message")]
fn expected_specific_panic() {
    panic!("specific message here");
}

Controlling Test Output

# แสดง println! output (ปกติซ่อน)
cargo test -- --show-output

# รัน tests แบบ sequential (ไม่ parallel)
cargo test -- --test-threads=1

# รัน test ที่ชื่อตรง
cargo test --exact test_name

# รัน tests ที่มีคำนี้ในชื่อ
cargo test add

Test Module Organization

Pattern 1: Tests ใต้ function

#![allow(unused)]
fn main() {
pub fn calculate(x: i32) -> i32 {
    x * 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calculate() {
        assert_eq!(calculate(5), 10);
    }
}
}

Pattern 2: Test submodules

#![allow(unused)]
fn main() {
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }

#[cfg(test)]
mod tests {
    use super::*;

    mod add_tests {
        use super::*;

        #[test]
        fn test_positive() {
            assert_eq!(add(2, 3), 5);
        }

        #[test]
        fn test_negative() {
            assert_eq!(add(-2, -3), -5);
        }
    }

    mod sub_tests {
        use super::*;

        #[test]
        fn test_basic() {
            assert_eq!(sub(5, 3), 2);
        }
    }
}
}

Test Helper Functions

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    // Helper - ไม่ใช่ test
    fn create_test_data() -> Vec<i32> {
        vec![1, 2, 3, 4, 5]
    }

    fn assert_even(n: i32) {
        assert!(n % 2 == 0, "{} is not even", n);
    }

    #[test]
    fn test_with_helpers() {
        let data = create_test_data();
        let sum: i32 = data.iter().sum();
        assert_even(sum); // ไม่ผ่าน เพราะ 15 ไม่ใช่เลขคู่
    }
}
}

Doc Tests

Comments ที่มี code examples จะถูก test:

#![allow(unused)]
fn main() {
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = my_lib::add(2, 3);
/// assert_eq!(result, 5);
/// ```text
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// # Panics
///
/// Panics if divisor is zero.
///
/// ```should_panic
/// my_lib::divide(10, 0);
/// ```text
pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division by zero");
    }
    a / b
}

/// Code that shouldn't compile:
///
/// ```compile_fail
/// let x: i32 = "not a number";
/// ```text
pub fn example() {}
}

Run Doc Tests

# รันเฉพาะ doc tests
cargo test --doc

Test Fixtures และ Setup

use std::fs;
use std::io::Write;

struct TestFile {
    path: String,
}

impl TestFile {
    fn new(name: &str, content: &str) -> Self {
        let path = format!("/tmp/test_{}", name);
        let mut file = fs::File::create(&path).unwrap();
        file.write_all(content.as_bytes()).unwrap();
        TestFile { path }
    }
}

impl Drop for TestFile {
    fn drop(&mut self) {
        fs::remove_file(&self.path).ok();
    }
}

#[test]
fn test_with_fixture() {
    let file = TestFile::new("example.txt", "Hello, World!");

    let content = fs::read_to_string(&file.path).unwrap();
    assert_eq!(content, "Hello, World!");
} // file ถูก cleanup อัตโนมัติ

ลองทำดู! 🎯

  1. ใช้ #[ignore] กับ slow tests
  2. สร้าง test helper functions
  3. เขียน doc tests

สรุปบทที่ 12

AttributePurpose
#[test]Mark as test
#[cfg(test)]Compile only for test
#[ignore]Skip test
#[should_panic]Expect panic

Cargo Commands

CommandDescription
cargo testRun all tests
cargo test NAMERun matching tests
cargo test --docRun doc tests only
cargo test -- --ignoredRun ignored tests
cargo test -- --show-outputShow println!
cargo test -- --test-threads=1Sequential

Test Organization Tips

  1. ใช้ #[cfg(test)] สำหรับ unit tests
  2. แยก integration tests ไปไว้ใน tests/
  3. ใช้ #[ignore] สำหรับ slow/flaky tests
  4. เขียน doc tests สำหรับ public API

👉 ต่อไป: บทที่ 13: Iterators & Closures

บทที่ 13: Iterators & Closures

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
ClosuresAnonymous functions
Iteratorsการประมวลผลแบบ lazy
Iterator Methodsmap, filter, fold
Custom Iteratorsสร้าง iterator เอง

👉 Closures

Closures

Closures คือ anonymous functions ที่สามารถ capture ค่าจาก environment รอบข้าง

Syntax พื้นฐาน

fn main() {
    // Closure พื้นฐาน
    let add_one = |x| x + 1;
    println!("{}", add_one(5));  // 6

    // กับ type annotations
    let add_two = |x: i32| -> i32 { x + 2 };
    println!("{}", add_two(5));  // 7

    // หลาย parameters
    let add = |a, b| a + b;
    println!("{}", add(2, 3));  // 5

    // ไม่มี parameters
    let say_hi = || println!("Hi!");
    say_hi();

    // Multi-line closure
    let calculate = |x: i32, y: i32| {
        let sum = x + y;
        let product = x * y;
        sum + product
    };
    println!("{}", calculate(3, 4));  // 7 + 12 = 19
}

Closure vs Function

#![allow(unused)]
fn main() {
fn add_one_fn(x: i32) -> i32 { x + 1 }
let add_one_closure = |x: i32| x + 1;

// ทั้งสองใช้เหมือนกัน
println!("{}", add_one_fn(5));       // 6
println!("{}", add_one_closure(5));  // 6
}
AspectFunctionClosure
Capture❌ ไม่ได้✅ ได้
Syntaxfn name()|x| x + 1
Inline
Generic❌ (ต้องแยก)✅ (type inference)

Capture Environment

Closures สามารถ “จับ” ค่าจากภายนอกได้:

fn main() {
    let x = 4;
    let multiplier = 3;

    // capture x และ multiplier
    let calculate = |y| x + y * multiplier;

    println!("{}", calculate(2));  // 4 + 2*3 = 10

    // x และ multiplier ยังใช้ได้ (ถูก borrow)
    println!("x = {}, multiplier = {}", x, multiplier);
}

3 Capture Modes

Rust มี 3 วิธีที่ closure capture ค่า:

1. Fn - Borrow (&T)

fn main() {
    let list = vec![1, 2, 3];

    // Fn: borrow list
    let print_list = || println!("{:?}", list);

    print_list();
    print_list();  // เรียกได้หลายครั้ง

    println!("Still valid: {:?}", list);  // ยังใช้ได้
}

2. FnMut - Mutable Borrow (&mut T)

fn main() {
    let mut count = 0;

    // FnMut: mutable borrow count
    let mut increment = || {
        count += 1;
        println!("Count: {}", count);
    };

    increment();  // Count: 1
    increment();  // Count: 2
    increment();  // Count: 3

    println!("Final: {}", count);  // 3
}

3. FnOnce - Move Ownership (T)

fn main() {
    let message = String::from("Hello");

    // FnOnce: move ownership
    let consume = move || {
        println!("{}", message);
        // message ถูก drop เมื่อ closure จบ
    };

    consume();
    // consume();  // ❌ Error: ใช้ได้แค่ครั้งเดียว

    // println!("{}", message);  // ❌ message ถูก move ไปแล้ว
}

move Keyword

บังคับให้ closure เป็นเจ้าของค่า:

fn main() {
    let name = String::from("Alice");

    // Without move: borrow
    let greet1 = || println!("Hello, {}!", name);
    greet1();
    println!("Name still valid: {}", name);

    // With move: take ownership
    let greet2 = move || println!("Hello, {}!", name);
    greet2();
    // println!("{}", name);  // ❌ name moved
}

ใช้ move กับ Threads

use std::thread;

fn main() {
    let data = vec![1, 2, 3];

    // ต้อง move เพราะ thread อาจอยู่นานกว่า main
    let handle = thread::spawn(move || {
        println!("Data in thread: {:?}", data);
    });

    handle.join().unwrap();
}

Closures as Parameters

ใช้ Trait Bounds

fn apply<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn apply_twice<F>(mut f: F)
where
    F: FnMut(),
{
    f();
    f();
}

fn apply_many<F>(f: F, times: u32)
where
    F: Fn(),
{
    for _ in 0..times {
        f();
    }
}

fn main() {
    apply(|| println!("Called once!"));
    apply_twice(|| println!("Called!"));
    apply_many(|| println!("Hello!"), 3);
}

Trait Hierarchy

FnOnce  (ทุก closure เป็น FnOnce)
  ↓
FnMut   (ถ้าไม่ move ownership)
  ↓
Fn      (ถ้าไม่ mutate)

Closures as Return Values

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

fn main() {
    let add_5 = make_adder(5);
    let add_10 = make_adder(10);

    println!("{}", add_5(3));   // 8
    println!("{}", add_10(3));  // 13
}

ตัวอย่างจริง: Map และ Filter

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // map กับ closure
    let doubled: Vec<i32> = numbers
        .iter()
        .map(|x| x * 2)
        .collect();
    println!("Doubled: {:?}", doubled);  // [2, 4, 6, 8, 10]

    // filter กับ closure
    let evens: Vec<&i32> = numbers
        .iter()
        .filter(|x| *x % 2 == 0)
        .collect();
    println!("Evens: {:?}", evens);  // [2, 4]

    // Combined
    let sum_of_squares: i32 = numbers
        .iter()
        .map(|x| x * x)
        .filter(|x| x % 2 == 0)
        .sum();
    println!("Sum of even squares: {}", sum_of_squares);  // 4 + 16 = 20
}

ลองทำดู! 🎯

  1. สร้าง closure ที่ capture mutable variable และเพิ่มค่า
  2. สร้าง function ที่รับ closure เป็น parameter
  3. ใช้ move กับ thread::spawn

สรุป

TraitCaptureเรียกได้ใช้เมื่อ
Fn&Tหลายครั้งไม่ mutate
FnMut&mut Tหลายครั้งmutate ได้
FnOnceT1 ครั้งmove ownership
KeywordEffect
moveบังคับ take ownership
|x|closure parameters
impl Fn()return closure

👉 ต่อไป: Iterators

Iterators

Iterator ประมวลผล collection ทีละ item แบบ lazy (ไม่ทำจนกว่าจะต้องใช้)

🔗 Iterator Chain Visualization

Iterator Chain Diagram


Iterator Trait

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;

    // ... มี methods อื่นๆ อีกมากที่ได้มาฟรี
}

ทุกครั้งที่เรียก next():

  • Some(item) - ได้ item ถัดไป
  • None - หมดแล้ว

Basic Usage

fn main() {
    let v = vec![1, 2, 3];

    // สร้าง iterator
    let mut iter = v.iter();

    // เรียก next() ทีละครั้ง
    assert_eq!(iter.next(), Some(&1));
    assert_eq!(iter.next(), Some(&2));
    assert_eq!(iter.next(), Some(&3));
    assert_eq!(iter.next(), None);  // หมดแล้ว
    assert_eq!(iter.next(), None);  // ยังคง None
}

3 วิธีสร้าง Iterator

1. iter() - Borrow (&T)

fn main() {
    let v = vec![1, 2, 3];

    for x in v.iter() {
        println!("{}", x);  // x is &i32
    }

    // v ยังใช้ได้
    println!("Still valid: {:?}", v);
}

2. iter_mut() - Mutable Borrow (&mut T)

fn main() {
    let mut v = vec![1, 2, 3];

    for x in v.iter_mut() {
        *x *= 2;  // แก้ไขค่าได้
    }

    println!("{:?}", v);  // [2, 4, 6]
}

3. into_iter() - Take Ownership (T)

fn main() {
    let v = vec![String::from("a"), String::from("b")];

    for s in v.into_iter() {
        println!("{}", s);  // s is String (owned)
    }

    // println!("{:?}", v);  // ❌ v ถูก move ไปแล้ว
}

เปรียบเทียบ

MethodReturnsOwnership
iter()&TBorrow
iter_mut()&mut TMutable borrow
into_iter()TTake ownership

for Loop กับ Iterator

fn main() {
    let v = vec![1, 2, 3];

    // ทั้งสองเหมือนกัน:

    // 1. for loop (syntactic sugar)
    for x in &v {
        println!("{}", x);
    }

    // 2. Explicit iterator
    let mut iter = v.iter();
    while let Some(x) = iter.next() {
        println!("{}", x);
    }
}

Lazy Evaluation

Iterator ไม่ทำอะไรจนกว่าจะ “consume”:

fn main() {
    let v = vec![1, 2, 3];

    // ยังไม่ทำอะไร! (lazy)
    let iter = v.iter().map(|x| {
        println!("Processing {}", x);
        x * 2
    });

    println!("Iterator created, nothing printed yet...");

    // consume iterator
    let result: Vec<_> = iter.collect();
    // NOW it prints "Processing 1, 2, 3"

    println!("Result: {:?}", result);
}

Range as Iterator

fn main() {
    // Range เป็น iterator
    for i in 0..5 {
        println!("{}", i);  // 0, 1, 2, 3, 4
    }

    // Inclusive range
    for i in 0..=5 {
        println!("{}", i);  // 0, 1, 2, 3, 4, 5
    }

    // Use with methods
    let sum: i32 = (1..=100).sum();
    println!("Sum 1-100: {}", sum);  // 5050
}

Common Iterators

fn main() {
    // chars() - iterate characters
    for c in "hello".chars() {
        println!("{}", c);
    }

    // bytes() - iterate bytes
    for b in "hello".bytes() {
        println!("{}", b);
    }

    // lines() - iterate lines
    let text = "line1\nline2\nline3";
    for line in text.lines() {
        println!("{}", line);
    }

    // enumerate() - with index
    let v = vec!["a", "b", "c"];
    for (i, x) in v.iter().enumerate() {
        println!("{}: {}", i, x);
    }
}

Infinite Iterators

fn main() {
    // repeat - ซ้ำค่าเดิมตลอด
    let threes: Vec<i32> = std::iter::repeat(3).take(5).collect();
    println!("{:?}", threes);  // [3, 3, 3, 3, 3]

    // cycle - วนซ้ำ
    let cycle: Vec<i32> = vec![1, 2].into_iter().cycle().take(6).collect();
    println!("{:?}", cycle);  // [1, 2, 1, 2, 1, 2]
}

ลองทำดู! 🎯

  1. ใช้ iter(), iter_mut(), into_iter() ดูความแตกต่าง
  2. สร้าง range iterator และ sum
  3. ใช้ enumerate() กับ vector

สรุป

ConceptDescription
Iterator traitnext() -> Option<Item>
iter()Borrow elements
iter_mut()Mutable borrow
into_iter()Take ownership
Lazyไม่ทำจนกว่า consume
Infiniterepeat, cycle

👉 ต่อไป: Iterator Methods

Iterator Methods

Methods ของ Iterator แบ่งเป็น 2 ประเภท: Adapters และ Consumers

Adapters (แปลง Iterator)

Adapters รับ iterator แล้ว return iterator ใหม่:

map - แปลงค่า

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let doubled: Vec<i32> = v.iter()
        .map(|x| x * 2)
        .collect();

    println!("{:?}", doubled);  // [2, 4, 6, 8, 10]
}

filter - เลือกเฉพาะที่ต้องการ

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6];

    let evens: Vec<&i32> = v.iter()
        .filter(|x| *x % 2 == 0)
        .collect();

    println!("{:?}", evens);  // [2, 4, 6]
}

filter_map - filter + map รวมกัน

fn main() {
    let strings = vec!["1", "two", "3", "four", "5"];

    let numbers: Vec<i32> = strings.iter()
        .filter_map(|s| s.parse().ok())  // parse เฉพาะที่ได้
        .collect();

    println!("{:?}", numbers);  // [1, 3, 5]
}

enumerate - เพิ่ม index

fn main() {
    let v = vec!["a", "b", "c"];

    for (index, value) in v.iter().enumerate() {
        println!("{}: {}", index, value);
    }
    // 0: a
    // 1: b
    // 2: c
}

zip - จับคู่ 2 iterators

fn main() {
    let names = vec!["Alice", "Bob", "Charlie"];
    let scores = vec![95, 87, 92];

    for (name, score) in names.iter().zip(scores.iter()) {
        println!("{}: {}", name, score);
    }
    // Alice: 95
    // Bob: 87
    // Charlie: 92
}

take / skip

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // take: เอา n ตัวแรก
    let first_3: Vec<_> = v.iter().take(3).collect();
    println!("First 3: {:?}", first_3);  // [1, 2, 3]

    // skip: ข้าม n ตัวแรก
    let after_5: Vec<_> = v.iter().skip(5).collect();
    println!("After 5: {:?}", after_5);  // [6, 7, 8, 9, 10]

    // take_while / skip_while
    let small: Vec<_> = v.iter().take_while(|x| **x < 5).collect();
    println!("While < 5: {:?}", small);  // [1, 2, 3, 4]
}

flatten - แบน nested

fn main() {
    let nested = vec![vec![1, 2], vec![3, 4], vec![5]];

    let flat: Vec<i32> = nested.into_iter().flatten().collect();
    println!("{:?}", flat);  // [1, 2, 3, 4, 5]

    // flat_map = map + flatten
    let words = vec!["hello", "world"];
    let chars: Vec<char> = words.iter()
        .flat_map(|s| s.chars())
        .collect();
    println!("{:?}", chars);  // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
}

chain - ต่อ 2 iterators

fn main() {
    let a = vec![1, 2, 3];
    let b = vec![4, 5, 6];

    let combined: Vec<_> = a.iter().chain(b.iter()).collect();
    println!("{:?}", combined);  // [1, 2, 3, 4, 5, 6]
}

rev - กลับลำดับ

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let reversed: Vec<_> = v.iter().rev().collect();
    println!("{:?}", reversed);  // [5, 4, 3, 2, 1]
}

Consumers (ใช้ Iterator)

Consumers รับ iterator และ return ค่า (ไม่ใช่ iterator):

collect - รวบรวมเป็น collection

fn main() {
    let v: Vec<i32> = (1..=5).collect();
    let set: std::collections::HashSet<_> = (1..=5).collect();

    println!("Vec: {:?}", v);    // [1, 2, 3, 4, 5]
    println!("Set: {:?}", set);  // {1, 2, 3, 4, 5}
}

sum / product

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    let total: i32 = numbers.iter().sum();
    println!("Sum: {}", total);  // 15

    let product: i32 = numbers.iter().product();
    println!("Product: {}", product);  // 120
}

fold - รวมค่าแบบ custom

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // fold(initial, |accumulator, item| new_accumulator)
    let sum = numbers.iter().fold(0, |acc, x| acc + x);
    println!("Sum: {}", sum);  // 15

    // ใช้ fold สร้าง string
    let s = numbers.iter().fold(String::new(), |acc, x| {
        if acc.is_empty() {
            x.to_string()
        } else {
            format!("{}, {}", acc, x)
        }
    });
    println!("String: {}", s);  // "1, 2, 3, 4, 5"
}

reduce - เหมือน fold แต่ไม่มี initial

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    let max = numbers.iter().reduce(|a, b| if a > b { a } else { b });
    println!("Max: {:?}", max);  // Some(5)
}

count / min / max

fn main() {
    let v = vec![3, 1, 4, 1, 5, 9, 2, 6];

    println!("Count: {}", v.iter().count());      // 8
    println!("Min: {:?}", v.iter().min());        // Some(1)
    println!("Max: {:?}", v.iter().max());        // Some(9)
}

find / position

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    // find - หาค่าแรกที่ตรง
    let first_even = v.iter().find(|x| *x % 2 == 0);
    println!("First even: {:?}", first_even);  // Some(2)

    // position - หา index
    let pos = v.iter().position(|x| *x == 3);
    println!("Position of 3: {:?}", pos);  // Some(2)
}

any / all

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let has_even = v.iter().any(|x| x % 2 == 0);
    println!("Has even: {}", has_even);  // true

    let all_positive = v.iter().all(|x| *x > 0);
    println!("All positive: {}", all_positive);  // true
}

for_each - side effects

fn main() {
    let v = vec![1, 2, 3];

    v.iter().for_each(|x| println!("Value: {}", x));
}

Chaining (ต่อเชื่อม)

fn main() {
    let data = vec!["1", "two", "3", "four", "5", "six"];

    let result: i32 = data.iter()
        .filter_map(|s| s.parse::<i32>().ok())  // parse เฉพาะตัวเลข
        .filter(|n| n % 2 == 1)                  // เอาเฉพาะเลขคี่
        .map(|n| n * n)                          // ยกกำลังสอง
        .sum();                                   // รวม

    println!("Sum of odd squares: {}", result);  // 1 + 9 + 25 = 35
}

ลองทำดู! 🎯

  1. ใช้ map และ filter chain กัน
  2. ใช้ enumerate และ zip
  3. ใช้ fold หาค่า max เอง

สรุป

Adapters (return Iterator)

MethodPurpose
mapแปลงค่า
filterเลือกค่า
enumerateเพิ่ม index
zipจับคู่
take / skipเอา/ข้าม n ตัว
flattenแบน nested
chainต่อ iterators
revกลับลำดับ

Consumers (return Value)

MethodPurpose
collectรวมเป็น collection
sum / productรวม/คูณ
foldcustom aggregation
count / min / maxนับ/หาเล็ก/ใหญ่
find / positionหาค่า/ตำแหน่ง
any / allตรวจเงื่อนไข

👉 ต่อไป: Custom Iterators

Custom Iterators

สร้าง iterator ของตัวเอง โดย implement Iterator trait

Implementation พื้นฐาน

struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Counter {
        Counter { count: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;  // ระบุ type ที่จะ return

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None  // หมดแล้ว
        }
    }
}

fn main() {
    let counter = Counter::new(5);

    for n in counter {
        println!("{}", n);
    }
    // Output: 1, 2, 3, 4, 5
}

ใช้ Iterator Methods ได้ฟรี

เมื่อ implement next() ได้ Iterator methods ทั้งหมดมาฟรี:

fn main() {
    // sum
    let sum: u32 = Counter::new(5).sum();
    println!("Sum: {}", sum);  // 15

    // filter
    let evens: Vec<u32> = Counter::new(10)
        .filter(|x| x % 2 == 0)
        .collect();
    println!("Evens: {:?}", evens);  // [2, 4, 6, 8, 10]

    // map
    let squared: Vec<u32> = Counter::new(5)
        .map(|x| x * x)
        .collect();
    println!("Squared: {:?}", squared);  // [1, 4, 9, 16, 25]

    // chain กัน
    let result: u32 = Counter::new(10)
        .filter(|x| x % 2 == 0)
        .map(|x| x * x)
        .sum();
    println!("Sum of even squares: {}", result);  // 220
}

ตัวอย่าง: Fibonacci Iterator

struct Fibonacci {
    curr: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Fibonacci {
        Fibonacci { curr: 0, next: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let result = self.curr;

        self.curr = self.next;
        self.next = result + self.next;

        Some(result)  // Infinite iterator!
    }
}

fn main() {
    // Take first 10 Fibonacci numbers
    let fibs: Vec<u64> = Fibonacci::new().take(10).collect();
    println!("{:?}", fibs);
    // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

    // Find first Fibonacci > 100
    let first_over_100 = Fibonacci::new()
        .find(|&x| x > 100);
    println!("First > 100: {:?}", first_over_100);  // Some(144)
}

IntoIterator Trait

ทำให้ใช้กับ for loop ได้:

struct MyCollection {
    items: Vec<i32>,
}

impl IntoIterator for MyCollection {
    type Item = i32;
    type IntoIter = std::vec::IntoIter<i32>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}

// สำหรับ reference
impl<'a> IntoIterator for &'a MyCollection {
    type Item = &'a i32;
    type IntoIter = std::slice::Iter<'a, i32>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.iter()
    }
}

fn main() {
    let collection = MyCollection { items: vec![1, 2, 3] };

    // ใช้กับ for loop ได้
    for item in &collection {
        println!("{}", item);
    }

    // ยัง borrow ได้
    println!("Length: {}", collection.items.len());
}

DoubleEndedIterator

ทำให้ rev() ใช้ได้:

struct Range {
    start: i32,
    end: i32,
}

impl Range {
    fn new(start: i32, end: i32) -> Range {
        Range { start, end }
    }
}

impl Iterator for Range {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.start < self.end {
            let result = self.start;
            self.start += 1;
            Some(result)
        } else {
            None
        }
    }
}

impl DoubleEndedIterator for Range {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.start < self.end {
            self.end -= 1;
            Some(self.end)
        } else {
            None
        }
    }
}

fn main() {
    // Forward
    let forward: Vec<i32> = Range::new(1, 5).collect();
    println!("Forward: {:?}", forward);  // [1, 2, 3, 4]

    // Reverse
    let backward: Vec<i32> = Range::new(1, 5).rev().collect();
    println!("Backward: {:?}", backward);  // [4, 3, 2, 1]
}

ExactSizeIterator

ถ้ารู้ขนาด:

impl ExactSizeIterator for Counter {
    fn len(&self) -> usize {
        (self.max - self.count) as usize
    }
}

fn main() {
    let counter = Counter::new(10);
    println!("Size: {}", counter.len());  // 10
}

ตัวอย่างจริง: Lines Iterator

struct Lines<'a> {
    remaining: &'a str,
}

impl<'a> Lines<'a> {
    fn new(s: &'a str) -> Lines<'a> {
        Lines { remaining: s }
    }
}

impl<'a> Iterator for Lines<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        if self.remaining.is_empty() {
            return None;
        }

        match self.remaining.find('\n') {
            Some(pos) => {
                let line = &self.remaining[..pos];
                self.remaining = &self.remaining[pos + 1..];
                Some(line)
            }
            None => {
                let line = self.remaining;
                self.remaining = "";
                Some(line)
            }
        }
    }
}

fn main() {
    let text = "line1\nline2\nline3";

    for line in Lines::new(text) {
        println!("Line: {}", line);
    }
}

ลองทำดู! 🎯

  1. สร้าง iterator ที่นับถอยหลัง
  2. สร้าง Fibonacci iterator และ take(20)
  3. Implement IntoIterator สำหรับ custom struct

สรุปบทที่ 13

TraitPurposeMethod
IteratorBasic iterationnext()
IntoIteratorConvert to iteratorinto_iter()
DoubleEndedIteratorReversenext_back()
ExactSizeIteratorKnown sizelen()

Key Points

  • implement next() → ได้ methods ฟรี
  • type Item ระบุ type ที่ return
  • IntoIterator ทำให้ใช้กับ for ได้

👉 ต่อไป: บทที่ 14: Smart Pointers

บทที่ 14: Smart Pointers

Smart pointers คือ structs ที่ทำตัวเหมือน pointers แต่มีความสามารถเพิ่มเติม

สิ่งที่จะได้เรียนรู้

Typeคำอธิบาย
Box<T>Heap allocation
Rc<T>Reference counting
RefCell<T>Interior mutability
Weak<T>ป้องกัน cycles

👉 Box<T>

Box<T>

Box<T> เก็บข้อมูลบน heap แทน stack เป็น smart pointer ที่ง่ายที่สุด

เมื่อไหร่ใช้ Box?

  1. Recursive types - type ที่มีตัวเองข้างใน
  2. Large data - ย้ายข้อมูลใหญ่ไป heap
  3. Trait objects - dynamic dispatch
  4. Transfer ownership โดยไม่ copy

📦 Smart Pointer Comparison

Smart Pointers Diagram


การใช้งานพื้นฐาน

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);

    // Box implements Deref ใช้เหมือน reference
    let x = *b + 1;
    println!("x = {}", x);
}

Use Case 1: Recursive Types

ปัญหา: ขนาดไม่แน่นอน

#![allow(unused)]
fn main() {
// ❌ Error: recursive type has infinite size
enum List {
    Cons(i32, List),
    Nil,
}
}

Compiler ไม่รู้ว่า List ใหญ่เท่าไหร่

ทางแก้: ใช้ Box

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));

    // Print list
    print_list(&list);
}

fn print_list(list: &List) {
    match list {
        Cons(value, next) => {
            println!("{}", value);
            print_list(next);
        }
        Nil => println!("End"),
    }
}

ทำไมใช้ได้? Box มีขนาดคงที่ (pointer size) ไม่ว่าจะชี้ไปที่อะไร

Stack                    Heap
+-------------+         +-------------+
| Cons(1, ----+-------->| Cons(2, ----+--->...
+-------------+         +-------------+

Use Case 2: Trait Objects

trait Draw {
    fn draw(&self);
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Draw for Circle {
    fn draw(&self) {
        println!("Drawing circle with radius {}", self.radius);
    }
}

impl Draw for Square {
    fn draw(&self) {
        println!("Drawing square with side {}", self.side);
    }
}

fn main() {
    // Vec ของหลาย types ที่ implement Draw
    let shapes: Vec<Box<dyn Draw>> = vec![
        Box::new(Circle { radius: 5.0 }),
        Box::new(Square { side: 3.0 }),
    ];

    for shape in &shapes {
        shape.draw();
    }
}

Use Case 3: Large Data Transfer

fn main() {
    // Large array on stack
    let big_array = [0u8; 1_000_000]; // 1MB on stack!

    // Move to heap with Box
    let boxed_array = Box::new([0u8; 1_000_000]); // 1MB on heap

    // Now ownership transfer is cheap (just move pointer)
    let another_owner = boxed_array; // Fast move!
}

Deref Trait

Box implements Deref ทำให้ใช้งานเหมือน reference:

use std::ops::Deref;

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y); // Deref แปลง Box<T> -> T

    // Deref coercion
    let s = Box::new(String::from("hello"));
    hello(&s); // Box<String> -> &String -> &str
}

fn hello(name: &str) {
    println!("Hello, {}!", name);
}

Deref Coercion

Rust แปลง types อัตโนมัติผ่าน Deref chain:

// Box<String> -> String -> str
// &Box<String> -> &String -> &str

fn main() {
    let b = Box::new(String::from("Rust"));

    // ทั้งหมดนี้ใช้ได้เหมือนกัน
    greet(&b);           // &Box<String>
    greet(&*b);          // &String
    greet(&(*b)[..]);    // &str
}

fn greet(s: &str) {
    println!("Hello, {}!", s);
}

Drop Trait

Box จะ drop ทั้ง pointer และ data บน heap เมื่อออกจาก scope:

fn main() {
    {
        let b = Box::new(String::from("hello"));
        println!("{}", b);
    } // b dropped here → String on heap is freed

    println!("Box is dropped!");
}

Custom Drop

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}
// Output:
// CustomSmartPointers created.
// Dropping CustomSmartPointer with data `other stuff`!
// Dropping CustomSmartPointer with data `my stuff`!

Drop Early with std::mem::drop

fn main() {
    let c = Box::new(String::from("hello"));

    println!("Before drop");
    drop(c);  // Drop early
    println!("After drop");

    // println!("{}", c); // ❌ Error: c was moved
}

เปรียบเทียบ Box กับ Stack

AspectStackBox (Heap)
Sizeต้องรู้ตอน compileได้ตอน runtime
Speedเร็วกว่าช้ากว่าเล็กน้อย
OwnershipCopy หรือ MoveMove pointer เท่านั้น
Use caseSmall, fixed-sizeLarge, recursive, dynamic

ลองทำดู! 🎯

  1. สร้าง Binary Tree ด้วย Box
  2. Implement trait object vector
  3. ลอง drop Box ก่อนออกจาก scope

สรุป

แนวคิดคำอธิบาย
Box::new(x)สร้าง Box ใส่ x บน heap
*boxDeref ดึงค่าออก
Recursive typesใช้ Box ให้ขนาดคงที่
Trait objectsBox\<dyn Trait\>
Dropอัตโนมัติเมื่อออกจาก scope

👉 ต่อไป: Rc<T>

Rc<T>

Rc<T> (Reference Counted) ให้หลาย owners ได้ ใช้เมื่อต้องการ share ownership

เมื่อไหร่ใช้ Rc?

ปกติ Rust มี owner เดียว แต่บางครั้งต้องการหลาย owners:

      +-----+
      |  a  |
      +--+--+
         |
      +--v--+
      |  5  |<-- want b and c to share this too
      +-----+
         ^
    +----+----+
 +--+--+   +--+--+
 |  b  |   |  c  |
 +-----+   +-----+

การใช้งาน

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("hello"));

    println!("Count after a: {}", Rc::strong_count(&a)); // 1

    let b = Rc::clone(&a);  // เพิ่ม reference count
    println!("Count after b: {}", Rc::strong_count(&a)); // 2

    {
        let c = Rc::clone(&a);
        println!("Count in block: {}", Rc::strong_count(&a)); // 3
    } // c dropped, count -= 1

    println!("Count after block: {}", Rc::strong_count(&a)); // 2
}

หมายเหตุ: Rc::clone ไม่ copy data แค่เพิ่ม reference count


Rc::clone vs .clone()

use std::rc::Rc;

fn main() {
    let a = Rc::new(vec![1, 2, 3]);

    // ✅ Preferred: ชัดเจนว่าเพิ่ม reference count
    let b = Rc::clone(&a);

    // ✅ ก็ใช้ได้ แต่อาจสับสนกับ deep clone
    let c = a.clone();

    // Both b and c point to the same data
}

Shared List Example

use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    // a = [5, 10, Nil]
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("Count after a: {}", Rc::strong_count(&a)); // 1

    // b = [3, -> a]
    let b = Cons(3, Rc::clone(&a));
    println!("Count after b: {}", Rc::strong_count(&a)); // 2

    // c = [4, -> a]
    let c = Cons(4, Rc::clone(&a));
    println!("Count after c: {}", Rc::strong_count(&a)); // 3

    // a ถูก share โดย b และ c
}
b: Cons(3, --+
             |     +-------------------------+
             +---->| a: Cons(5, Cons(10, Nil))|
             +---->|                         |
             |     +-------------------------+
c: Cons(4, --+

Rc<T> Properties

PropertyDescription
Single-threadedไม่ thread-safe
Immutableได้แค่ &T ไม่ได้ &mut T
No Copyใช้ Rc::clone()
Automatic dropเมื่อ count เป็น 0

Rc กับ Methods

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("hello"));

    // Deref ใช้ methods ของ inner type ได้
    println!("Length: {}", a.len());
    println!("Uppercase: {}", a.to_uppercase());

    // Rc methods
    println!("Strong count: {}", Rc::strong_count(&a));

    // Get reference
    let s: &String = &a;
    println!("Ref: {}", s);
}

Rc::downgrade และ Weak

use std::rc::{Rc, Weak};

fn main() {
    let strong = Rc::new(5);
    let weak: Weak<i32> = Rc::downgrade(&strong);

    println!("Strong count: {}", Rc::strong_count(&strong)); // 1
    println!("Weak count: {}", Rc::weak_count(&strong));     // 1

    // Weak ต้อง upgrade เป็น Rc ก่อนใช้
    if let Some(value) = weak.upgrade() {
        println!("Value: {}", value);
    }

    drop(strong); // strong dropped

    // Weak ยัง upgrade ไม่ได้
    assert!(weak.upgrade().is_none());
}

Weak ใช้ป้องกัน reference cycles (ดูบท 04-weak.md)


ข้อจำกัดของ Rc

use std::rc::Rc;

fn main() {
    let a = Rc::new(5);

    // ❌ Error: cannot borrow as mutable
    // *a += 1;

    // Rc ให้แค่ shared reference (&T)
    // ถ้าต้องการ mutate ใช้ร่วมกับ RefCell
}

Rc + RefCell Pattern

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    // Multiple owners + Interior mutability
    let value = Rc::new(RefCell::new(5));

    let a = Rc::clone(&value);
    let b = Rc::clone(&value);

    // ทั้ง a และ b สามารถ mutate ได้
    *a.borrow_mut() += 10;
    *b.borrow_mut() += 10;

    println!("Value: {}", value.borrow()); // 25
}

ลองทำดู! 🎯

  1. สร้าง shared data ด้วย Rc
  2. Print strong_count ขณะ clone และ drop
  3. ลอง combine Rc กับ RefCell

สรุป

Functionคำอธิบาย
Rc::new(v)สร้าง Rc ใหม่
Rc::clone(&rc)เพิ่ม reference count
Rc::strong_count(&rc)จำนวน strong references
Rc::weak_count(&rc)จำนวน weak references
Rc::downgrade(&rc)สร้าง Weak reference

เมื่อไหร่ใช้

SituationUse
Single ownerปกติ (ไม่ต้อง Rc)
Multiple owners, single-threadRc
Multiple owners, multi-threadArc (บทที่ 15)
Mutability neededRc + RefCell

👉 ต่อไป: RefCell<T>

RefCell<T>

RefCell<T> ให้ interior mutability - mutate ข้อมูลแม้มี immutable references

ปัญหา: Borrowing Rules ตอน Compile

fn main() {
    let x = 5;

    // ❌ Error at compile time
    // let y = &mut x; // cannot borrow as mutable
}

บางครั้งต้องการ mutate แม้ interface เป็น immutable


RefCell Basics

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    // borrow() returns Ref<T> (like &T)
    println!("Value: {}", data.borrow());

    // borrow_mut() returns RefMut<T> (like &mut T)
    *data.borrow_mut() += 1;

    println!("Value: {}", data.borrow()); // 6
}

Compile-time vs Runtime

Aspect&T / &mut TRefCell
CheckCompile-timeRuntime
ErrorCompile errorPanic
FlexibilityStrictFlexible
PerformanceNo overheadSmall overhead
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    let borrow1 = data.borrow();
    let borrow2 = data.borrow(); // ✅ OK - multiple immutable borrows

    println!("{} {}", borrow1, borrow2);

    drop(borrow1);
    drop(borrow2);

    let mut_borrow = data.borrow_mut();
    // let another = data.borrow_mut(); // 💥 Panic at runtime!
}

Borrowing Rules (Runtime)

เหมือนกับ compile-time rules:

  1. หลาย immutable borrows ได้
  2. หนึ่ง mutable borrow เท่านั้น
  3. ไม่มี mutable + immutable พร้อมกัน
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);

    // ✅ Multiple borrows (must be dropped before mut borrow)
    {
        let a = data.borrow();
        let b = data.borrow();
        println!("{:?} {:?}", a, b);
    }

    // ✅ Single mut borrow
    {
        let mut c = data.borrow_mut();
        c.push(4);
    }

    println!("{:?}", data.borrow());
}

try_borrow และ try_borrow_mut

ไม่ panic แต่ return Result:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    let borrow = data.borrow();

    match data.try_borrow_mut() {
        Ok(mut value) => *value += 1,
        Err(_) => println!("Already borrowed!"),
    }
}

Use Case: Mock Objects

use std::cell::RefCell;

trait Messenger {
    fn send(&self, msg: &str);
}

struct MockMessenger {
    messages: RefCell<Vec<String>>,
}

impl MockMessenger {
    fn new() -> MockMessenger {
        MockMessenger {
            messages: RefCell::new(vec![]),
        }
    }
}

impl Messenger for MockMessenger {
    fn send(&self, msg: &str) {
        // &self is immutable, but we can still mutate!
        self.messages.borrow_mut().push(String::from(msg));
    }
}

fn main() {
    let messenger = MockMessenger::new();
    messenger.send("Hello");
    messenger.send("World");

    assert_eq!(messenger.messages.borrow().len(), 2);
}

Rc + RefCell = Multiple Owners + Mutability

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    // Add another child to branch
    branch.children.borrow_mut().push(Rc::new(Node {
        value: 7,
        children: RefCell::new(vec![]),
    }));

    println!("leaf: {:#?}", leaf);
    println!("branch: {:#?}", branch);
}

Common Pattern: Shared Mutable State

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let counter = Rc::new(RefCell::new(0));

    let counter1 = Rc::clone(&counter);
    let counter2 = Rc::clone(&counter);

    *counter1.borrow_mut() += 1;
    *counter2.borrow_mut() += 1;
    *counter.borrow_mut() += 1;

    println!("Final count: {}", counter.borrow()); // 3
}

Cell<T> (simpler alternative)

สำหรับ Copy types:

use std::cell::Cell;

fn main() {
    let x = Cell::new(5);

    x.set(10);           // ไม่ต้อง borrow
    let value = x.get(); // Copy ออกมา

    println!("{}", value); // 10
}
TypeBest for
Cell<T>Copy types (i32, bool, etc.)
RefCell<T>Non-Copy types (String, Vec, etc.)

ลองทำดู! 🎯

  1. สร้าง struct ที่มี RefCell\<Vec\<String\>\>
  2. Implement method ที่ modify Vec ผ่าน &self
  3. ลอง borrow ผิด rule และดู panic

สรุป

MethodReturnPanics
borrow()Ref<T>If mutably borrowed
borrow_mut()RefMut<T>If any borrow exists
try_borrow()Result<Ref<T>, BorrowError>Never
try_borrow_mut()Result<RefMut<T>, BorrowMutError>Never

เลือกใช้

SituationUse
Need mutability through &selfRefCell
Copy typesCell
Thread-safeMutex
Multiple owners + mutabilityRc<RefCell<T>>

👉 ต่อไป: Weak<T>

Weak<T>

Weak<T> เป็น reference ที่ไม่นับ ใช้ป้องกัน reference cycles (memory leak)

ปัญหา: Reference Cycle

เมื่อ Rc ชี้หากันเป็นวงกลม:

   +--------------------------+
   |                          |
   v                          |
+------+    strong    +------+|
| Node |<------------>| Node ||
|  A   |              |  B   ||
+------+              +------+|
   |                          |
   +--------------------------+

When A and B go out of scope:
- A has strong reference from B -> not dropped
- B has strong reference from A -> not dropped
-> Memory leak!

ทางออก: ใช้ Weak

use std::rc::{Rc, Weak};

// Weak ไม่นับ reference
// ถ้า strong count เป็น 0 → data ถูก drop
// แม้จะยังมี weak references อยู่

Rc::downgrade และ Weak::upgrade

use std::rc::{Rc, Weak};

fn main() {
    let strong = Rc::new(5);

    // สร้าง Weak จาก Rc
    let weak: Weak<i32> = Rc::downgrade(&strong);

    println!("Strong count: {}", Rc::strong_count(&strong));  // 1
    println!("Weak count: {}", Rc::weak_count(&strong));      // 1

    // Weak ต้อง upgrade เป็น Rc ก่อนใช้
    if let Some(value) = weak.upgrade() {
        println!("Value: {}", value);  // 5
    }

    // Drop strong reference
    drop(strong);

    // Weak upgrade ไม่ได้แล้ว
    assert!(weak.upgrade().is_none());
    println!("Data is gone!");
}

ตัวอย่าง: Tree Structure

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,      // Weak ไป parent
    children: RefCell<Vec<Rc<Node>>>, // Strong ไป children
}

fn main() {
    // สร้าง leaf node
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf strong count: {}", Rc::strong_count(&leaf));  // 1

    // สร้าง branch node
    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    // ตั้ง parent ของ leaf (weak reference)
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("After linking:");
    println!("  leaf strong: {}", Rc::strong_count(&leaf));    // 2 (leaf + branch's child)
    println!("  branch strong: {}", Rc::strong_count(&branch)); // 1
    println!("  branch weak: {}", Rc::weak_count(&branch));     // 1 (leaf's parent)

    // Access parent
    if let Some(parent) = leaf.parent.borrow().upgrade() {
        println!("leaf's parent value: {}", parent.value);  // 5
    }
}

ทำไมถึงไม่ leak?

              branch (strong=1, weak=1)
                ^ Weak
                |
              +-+
              |
leaf ---------+ Strong (in children vec)

When branch goes out of scope:
1. branch's strong=0 -> branch is dropped
2. leaf's strong -1 (from branch's children)
3. leaf's strong=1 -> still alive
4. leaf's parent.upgrade() = None

เมื่อไหร่ใช้ Weak?

SituationUse
Parent → ChildrenRc (strong)
Children → ParentWeak
Observer patternWeak (observers)
CacheWeak (cached data)
Breaking cyclesWeak

ตัวอย่าง: Observer Pattern

use std::rc::{Rc, Weak};
use std::cell::RefCell;

trait Observer {
    fn notify(&self, message: &str);
}

struct Subject {
    observers: RefCell<Vec<Weak<dyn Observer>>>,
}

impl Subject {
    fn new() -> Subject {
        Subject {
            observers: RefCell::new(vec![]),
        }
    }

    fn subscribe(&self, observer: &Rc<dyn Observer>) {
        self.observers.borrow_mut().push(Rc::downgrade(observer));
    }

    fn notify_all(&self, message: &str) {
        // Clean up dead observers and notify living ones
        self.observers.borrow_mut().retain(|weak| {
            if let Some(observer) = weak.upgrade() {
                observer.notify(message);
                true  // keep
            } else {
                false // remove dead reference
            }
        });
    }
}

struct Logger;
impl Observer for Logger {
    fn notify(&self, message: &str) {
        println!("[LOG] {}", message);
    }
}

fn main() {
    let subject = Subject::new();

    {
        let logger: Rc<dyn Observer> = Rc::new(Logger);
        subject.subscribe(&logger);

        subject.notify_all("Hello");  // [LOG] Hello
    }  // logger dropped here

    subject.notify_all("World");  // Nothing printed, observer is gone
}

Weak Methods

MethodDescription
Rc::downgrade(&rc)สร้าง Weak จาก Rc
weak.upgrade()Option\<Rc\<T\>\> - None ถ้า data ถูก drop
Weak::new()สร้าง Weak ว่าง (upgrade = None เสมอ)
weak.strong_count()จำนวน strong refs (0 ถ้า dropped)
weak.weak_count()จำนวน weak refs

ลองทำดู! 🎯

  1. สร้าง doubly-linked list ด้วย Rc + Weak
  2. สร้าง observer pattern
  3. ลอง drop Rc แล้วดู Weak::upgrade()

สรุปบทที่ 14

TypeOwnershipCountUse Case
Box\<T\>Single-Heap allocation
Rc\<T\>SharedStrongMultiple owners
Weak\<T\>Non-owningWeakBreak cycles
RefCell\<T\>Single-Interior mutability

Patterns

// Tree data structure
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,      // Weak up
    children: RefCell<Vec<Rc<Node>>>, // Strong down
}

// Shared mutable state
Rc<RefCell<T>>

// Break reference cycles
Weak<T>

👉 ต่อไป: บทที่ 15: Concurrency

บทที่ 15: Concurrency - การทำงานพร้อมกัน

Rust ทำให้ concurrent programming ปลอดภัย

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Threadsสร้างและจัดการ threads
Message Passingส่งข้อมูลด้วย channels
Shared StateMutex และ Arc
Sync & SendMarker traits

👉 Threads

Threads

การสร้างและจัดการ threads ใน Rust

🧵 Thread Visualization

+-------------------------------------------------------------------+
|                    Multi-Threading in Rust                        |
+-------------------------------------------------------------------+
|                                                                   |
|   Main Thread                 Spawned Thread                      |
|   -----------                 --------------                      |
|       |                                                           |
|       | spawn -----------------------> |                          |
|       |                                |                          |
|       v                                v                          |
|   +-------+                       +-------+                       |
|   | Task1 |                       | TaskA |                       |
|   +-------+                       +-------+                       |
|       |                                |                          |
|   +-------+                       +-------+                       |
|   | Task2 |                       | TaskB |                       |
|   +-------+                       +-------+                       |
|       |                                |                          |
|       | <-------- join() -------------+                           |
|       |                                                           |
|       v                                                           |
|    Continue                                                       |
|                                                                   |
+-------------------------------------------------------------------+

สร้าง Thread

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("hi from spawned thread: {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..3 {
        println!("hi from main thread: {}", i);
        thread::sleep(Duration::from_millis(1));
    }

    // รอ thread จบ
    handle.join().unwrap();
}

Output (อาจต่างกันแต่ละครั้ง):

hi from main thread: 1
hi from spawned thread: 1
hi from main thread: 2
hi from spawned thread: 2
hi from spawned thread: 3
hi from spawned thread: 4

JoinHandle

thread::spawn returns JoinHandle<T>:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        // return value from thread
        42
    });

    // join() returns Result<T, E>
    let result = handle.join().unwrap();
    println!("Thread returned: {}", result);
}

รอหลาย Threads

use std::thread;

fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = thread::spawn(move || {
            println!("Thread {} starting", i);
            i * 2
        });
        handles.push(handle);
    }

    // รอทุก threads จบ
    let results: Vec<i32> = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .collect();

    println!("Results: {:?}", results);
}

Move Closure

ใช้ move เพื่อย้าย ownership เข้า thread:

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    // ❌ Error: closure may outlive the current function
    // let handle = thread::spawn(|| {
    //     println!("vector: {:?}", v);
    // });

    // ✅ ใช้ move
    let handle = thread::spawn(move || {
        println!("vector: {:?}", v);
    });

    // println!("{:?}", v); // ❌ v was moved

    handle.join().unwrap();
}

Thread Builder

กำหนด thread name และ stack size:

use std::thread;

fn main() {
    let builder = thread::Builder::new()
        .name("worker".to_string())
        .stack_size(4 * 1024 * 1024); // 4MB stack

    let handle = builder.spawn(|| {
        println!("Thread name: {:?}", thread::current().name());
    }).unwrap();

    handle.join().unwrap();
}

Thread Information

use std::thread;

fn main() {
    // Current thread info
    let current = thread::current();
    println!("Main thread name: {:?}", current.name());
    println!("Main thread id: {:?}", current.id());

    let handle = thread::spawn(|| {
        let current = thread::current();
        println!("Spawned thread name: {:?}", current.name());
        println!("Spawned thread id: {:?}", current.id());
    });

    handle.join().unwrap();

    // จำนวน CPU cores
    println!("Available parallelism: {:?}", thread::available_parallelism());
}

Panic Handling in Threads

Panic ใน thread หนึ่งไม่กระทบ threads อื่น:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        panic!("oops!");
    });

    // join() returns Err if thread panicked
    match handle.join() {
        Ok(_) => println!("Thread completed successfully"),
        Err(e) => println!("Thread panicked: {:?}", e),
    }

    println!("Main thread continues!");
}

Thread Parking

หยุด thread ชั่วคราว:

use std::thread;
use std::time::Duration;

fn main() {
    let parked_thread = thread::spawn(|| {
        println!("Thread starting, will park...");
        thread::park();  // รอจนกว่าจะถูก unpark
        println!("Thread unparked!");
    });

    thread::sleep(Duration::from_secs(1));
    println!("Main thread unparking the other thread");
    parked_thread.thread().unpark();

    parked_thread.join().unwrap();
}

Scoped Threads (Rust 1.63+)

ยืม data โดยไม่ต้อง move:

use std::thread;

fn main() {
    let numbers = vec![1, 2, 3];

    thread::scope(|s| {
        s.spawn(|| {
            println!("numbers: {:?}", numbers);  // borrow, not move!
        });

        s.spawn(|| {
            println!("length: {}", numbers.len());
        });
    });  // threads จบเมื่อ scope จบ

    // numbers ยังใช้ได้!
    println!("After scope: {:?}", numbers);
}

ตัวอย่างจริง: Parallel Computation

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let chunk_size = data.len() / 2;

    let (left, right) = data.split_at(chunk_size);

    thread::scope(|s| {
        let left_sum = s.spawn(|| -> i32 {
            left.iter().sum()
        });

        let right_sum = s.spawn(|| -> i32 {
            right.iter().sum()
        });

        let total = left_sum.join().unwrap() + right_sum.join().unwrap();
        println!("Total sum: {}", total);
    });
}

ลองทำดู! 🎯

  1. สร้าง 3 threads ที่ print message แล้วรอทุก thread จบ
  2. ใช้ thread::scope เพื่อ borrow data
  3. จัดการ panic ใน thread ด้วย join()

💪 Advanced Exercises

Exercise 1: Parallel Sum

// เขียน function ที่คำนวณ sum ของ vector โดยแบ่งงานให้ 4 threads
fn parallel_sum(numbers: Vec<i32>) -> i32 {
    // TODO: แบ่ง vector เป็น 4 ส่วน
    // TODO: สร้าง 4 threads คำนวณ sum แต่ละส่วน  
    // TODO: รวมผลลัพธ์
    todo!()
}
ดูเฉลย
use std::thread;

fn parallel_sum(numbers: Vec<i32>) -> i32 {
    let chunk_size = numbers.len() / 4;
    let chunks: Vec<Vec<i32>> = numbers
        .chunks(chunk_size)
        .map(|c| c.to_vec())
        .collect();
    
    let handles: Vec<_> = chunks
        .into_iter()
        .map(|chunk| {
            thread::spawn(move || chunk.iter().sum::<i32>())
        })
        .collect();
    
    handles.into_iter().map(|h| h.join().unwrap()).sum()
}

Exercise 2: Thread-Safe Counter

// สร้าง counter ที่หลาย threads เพิ่มค่าพร้อมกันได้
// Hint: ใช้ Arc<Mutex<i32>>
ดูเฉลย
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Result: {}", *counter.lock().unwrap()); // 10
}

📝 ทดสอบความเข้าใจ

Q1: ทำไมต้องใช้ move กับ closure ใน thread::spawn?

A: เพราะ thread อาจอยู่นานกว่า scope ที่สร้างมัน ดังนั้นต้อง move ownership ของ captured variables เข้าไปใน thread เพื่อป้องกัน dangling references

Q2: thread::scope ต่างจาก thread::spawn อย่างไร?

A: thread::scope รับประกันว่า threads ทั้งหมดจะจบก่อนออกจาก scope ทำให้สามารถ borrow data จาก parent scope ได้โดยไม่ต้อง move

Q3: จะจัดการ panic ใน thread อย่างไร?

A: ใช้ handle.join() ที่ return Result<T, Box<dyn Any>> หาก thread panic จะได้ Err ที่มีข้อมูล panic แทน


สรุป

Functionคำอธิบาย
thread::spawnสร้าง thread ใหม่
handle.join()รอ thread จบ
move || {}ย้าย ownership เข้า thread
thread::scopeScoped threads (borrow ได้)
thread::current()ข้อมูล current thread
thread::park()หยุด thread ชั่วคราว
thread::Builderกำหนด name/stack size

👉 ต่อไป: Message Passing

Message Passing

ส่งข้อมูลระหว่าง threads ด้วย channels

Basic Channel

Message Passing Channels Diagram

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

Multiple Messages

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec!["hi", "from", "thread"];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_millis(200));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

Multiple Producers

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx1 = tx.clone();

    thread::spawn(move || {
        tx.send("from thread 1").unwrap();
    });

    thread::spawn(move || {
        tx1.send("from thread 2").unwrap();
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

👉 ต่อไป: Shared State

Shared State

แชร์ข้อมูลระหว่าง threads ด้วย Mutex

Mutex

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    } // lock released here

    println!("m = {:?}", m);
}

⚠️ คำเตือน: อันตรายของ Concurrency

  • 🔴 Deadlock - 2 threads รอกันไปมา ไม่มีใครทำงานต่อ
  • 🔴 Data Race - หลาย threads แก้ไขข้อมูลพร้อมกัน
  • 🔴 Poison - Mutex ถูก poison ถ้า thread panic ขณะถือ lock
+---------------------------------------------------------+
|                    Deadlock Example                     |
+---------------------------------------------------------+
|  Thread A: lock(mutex1) -> wait lock(mutex2)             |
|  Thread B: lock(mutex2) -> wait lock(mutex1)             |
|  !! ทั้งสองรอกันตลอดไป!                                  |
+---------------------------------------------------------+

💡 Best Practices

  • ✅ Lock เร็วที่สุด, ปล่อยเร็วที่สุด
  • ✅ Lock ตามลำดับเดียวกันทุก thread
  • ✅ ใช้ try_lock() เพื่อหลีกเลี่ยง deadlock
  • ✅ ใช้ channels แทน shared state เมื่อเป็นไปได้

Arc + Mutex

Arc = Atomic Reference Counting (thread-safe Rc)

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    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 || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap()); // 10
}

👉 ต่อไป: Sync & Send

Sync & Send

Marker traits สำหรับ concurrency

Send

Type ที่ส่งระหว่าง threads ได้:

#![allow(unused)]
fn main() {
// Most types are Send
// Rc<T> is NOT Send (use Arc<T> instead)
}

Sync

Type ที่หลาย threads เข้าถึงพร้อมกันได้:

#![allow(unused)]
fn main() {
// T is Sync if &T is Send
// RefCell<T> is NOT Sync
// Mutex<T> IS Sync
}

สรุปบทที่ 15

แนวคิดใช้เมื่อ
thread::spawnสร้าง thread
channelส่งข้อมูลระหว่าง threads
Mutexป้องกัน data race
Arcแชร์ ownership ระหว่าง threads

👉 ต่อไป: บทที่ 16: Async/Await

บทที่ 16: Async/Await

Asynchronous programming สำหรับ I/O-bound tasks

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
async/awaitพื้นฐาน
FuturesFuture trait
TokioRuntime ยอดนิยม
Patternsselect!, join!

👉 Async พื้นฐาน

Async พื้นฐาน

Asynchronous programming ใน Rust ช่วยจัดการ I/O-bound tasks อย่างมีประสิทธิภาพ

⚡ Async vs Sync Visualization

Async State Machine Diagram

Sync vs Async

Synchronous (Blocking)

fn main() {
    let data1 = fetch_data_1(); // รอ... 2 seconds
    let data2 = fetch_data_2(); // รอ... 2 seconds
    // Total: 4 seconds
}

Asynchronous (Non-blocking)

async fn main() {
    let (data1, data2) = join!(
        fetch_data_1(), // เริ่มพร้อมกัน
        fetch_data_2()
    );
    // Total: ~2 seconds
}

async fn

async fn hello_world() {
    println!("Hello, world!");
}

// async fn returns a Future
// must be run by an executor

async fn returns impl Future - ไม่รันทันที ต้อง await หรือใช้ executor


.await

async fn fetch_number() -> i32 {
    // simulate async work
    42
}

async fn main() {
    let number = fetch_number().await; // รอ Future complete
    println!("Number: {}", number);
}

Sequential vs Concurrent

async fn learn_song() -> String {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    "La la la".to_string()
}

async fn sing_song(song: &str) {
    println!("Singing: {}", song);
}

async fn dance() {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    println!("Dancing!");
}

// Sequential
async fn sequential() {
    let song = learn_song().await; // 1 sec
    sing_song(&song).await;
    dance().await;                  // 1 sec
    // Total: 2+ seconds
}

// Concurrent - dance while learning
async fn concurrent() {
    let (song, _) = tokio::join!(
        learn_song(),  // start learning
        dance()        // dance at the same time
    );
    sing_song(&song).await;
    // Total: ~1 second (overlapped)
}

Runtime: Tokio

Rust ไม่มี async runtime ในตัว ต้องใช้ library:

# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }

Basic Usage

#[tokio::main]
async fn main() {
    println!("Hello from async main!");

    let result = do_something().await;
    println!("Result: {}", result);
}

async fn do_something() -> i32 {
    // simulate async work
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    42
}

Manual Runtime

fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    rt.block_on(async {
        println!("Running on tokio runtime");
    });
}

async Block

async fn example() {
    let future = async {
        // code here runs lazily
        println!("Inside async block");
        42
    };

    // future ยังไม่รัน!
    let result = future.await;
    println!("Result: {}", result);
}

Capture Variables

async fn example() {
    let name = String::from("Alice");

    // move ownership into async block
    let greeting = async move {
        format!("Hello, {}!", name)
    };

    // name moved, can't use here
    // println!("{}", name); // ❌

    println!("{}", greeting.await);
}

Futures are Lazy

async fn my_async_fn() {
    println!("This runs when awaited");
}

fn main() {
    let future = my_async_fn(); // ไม่รันอะไรเลย!

    // ต้อง await หรือใช้ executor
    // future.await; // or
    // tokio::runtime::Runtime::new().unwrap().block_on(future);
}

Return Types

use std::future::Future;

// These are equivalent:
async fn foo() -> i32 {
    42
}

fn bar() -> impl Future<Output = i32> {
    async {
        42
    }
}

Error Handling in Async

async fn fetch_data() -> Result<String, std::io::Error> {
    // simulate possible failure
    Ok("data".to_string())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = fetch_data().await?;
    println!("Data: {}", data);
    Ok(())
}

Async in Traits (Rust 1.75+)

trait AsyncFetcher {
    async fn fetch(&self) -> String;
}

struct MyFetcher;

impl AsyncFetcher for MyFetcher {
    async fn fetch(&self) -> String {
        "data".to_string()
    }
}

When to Use Async

SituationUse
I/O-bound (network, files)✅ Async
CPU-bound (computation)❌ Use threads
Need simple code❌ Stick with sync
High concurrency✅ Async

ลองทำดู! 🎯

  1. สร้าง async function ที่ return Result
  2. ใช้ tokio::time::sleep แล้ว await
  3. ลอง tokio::join! กับ 2 futures

สรุป

ConceptDescription
async fnReturns Future
.awaitWait for Future
async { }Async block
#[tokio::main]Async main entry
LazyFutures don’t run until awaited

Runtimes

RuntimeUse Case
TokioGeneral purpose, most popular
async-stdSimpler, std-like API
smolMinimal, lightweight

👉 ต่อไป: Futures

Futures

Future คือ trait หลักของ async programming ใน Rust

Future Trait

pub trait Future {
    type Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    Ready(T),   // Future เสร็จแล้ว มีค่า T
    Pending,    // ยังไม่เสร็จ ให้ poll อีกทีหลัง
}

ทำงานอย่างไร?

                    +-------------+
                    |   Future    |
                    +------+------+ 
                           | poll()
                           v
              +-------------+-------------+
              |                           |
        Poll::Ready(T)            Poll::Pending
        (เสร็จแล้ว)              (รอก่อน)
              |                           |
              v                           v
         ใช้ค่า T              Executor จะ poll อีกครั้งเมื่อพร้อม

async Block

สร้าง anonymous Future:

async fn example() {
    let future = async {
        println!("Inside async block");
        42
    };

    // future ยังไม่รัน!
    let result = future.await;  // รันตอนนี้
    println!("Result: {}", result);
}

Futures are Lazy

สำคัญมาก: Future ไม่ทำอะไรจนกว่าจะถูก poll

async fn do_something() {
    println!("This runs when polled!");
}

fn main() {
    let future = do_something(); // ไม่ print!

    // ต้อง await หรือใช้ executor
    // ไม่งั้น future จะไม่ทำอะไรเลย

    drop(future); // ทิ้งทั้งที่ยังไม่ได้รัน
}

Combine Futures

Sequential

async fn sequential() {
    let a = async_fn_1().await;  // รัน 1 ก่อน
    let b = async_fn_2().await;  // แล้วรัน 2
    println!("{} {}", a, b);
}

Concurrent กับ join!

use tokio::join;

async fn concurrent() {
    // รันพร้อมกัน!
    let (a, b) = join!(
        async_fn_1(),
        async_fn_2()
    );
    println!("{} {}", a, b);
}

Racing กับ select!

use tokio::select;

async fn race() {
    select! {
        result = async_fn_1() => println!("1 finished: {}", result),
        result = async_fn_2() => println!("2 finished: {}", result),
    }
    // แค่อันแรกที่เสร็จ
}

Pin คืออะไร?

Pin ป้องกัน Future ไม่ให้ถูก move ในหน่วยความจำ:

use std::pin::Pin;
use std::future::Future;

fn takes_future(future: Pin<&mut dyn Future<Output = i32>>) {
    // future ถูก pin ไว้จะ move ไม่ได้
}

ทำไมต้อง Pin?

  • บาง Future มี self-references
  • ถ้า move แล้ว references จะ invalid
  • Pin รับประกันว่าจะไม่ move

สร้าง Future เอง (Advanced)

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct MyFuture {
    count: u32,
}

impl Future for MyFuture {
    type Output = u32;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.count += 1;
        if self.count >= 3 {
            Poll::Ready(self.count)
        } else {
            cx.waker().wake_by_ref(); // บอกให้ poll อีกครั้ง
            Poll::Pending
        }
    }
}

async fn vs Future

// ทั้งสองเหมือนกัน:

async fn foo() -> i32 {
    42
}

fn bar() -> impl Future<Output = i32> {
    async { 42 }
}

ลองทำดู! 🎯

  1. สร้าง async function ที่ return String
  2. ใช้ join! รวม 2 futures
  3. ลองสร้าง future แล้วไม่ await ดูว่าเกิดอะไร

สรุป

ConceptDescription
FutureTrait สำหรับ async values
Poll::ReadyFuture เสร็จแล้ว
Poll::Pendingยังไม่เสร็จ
Lazyไม่รันจนกว่าจะ poll
Pinป้องกันการ move

👉 ต่อไป: Tokio Runtime

Tokio Runtime

Tokio เป็น async runtime ที่นิยมที่สุดใน Rust ecosystem

Setup

# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
```text

Features ที่มี:

| Feature           | Description                    |
| ----------------- | ------------------------------ |
| `rt`              | Runtime core                   |
| `rt-multi-thread` | Multi-threaded runtime         |
| `time`            | tokio::time                    |
| `net`             | TCP/UDP networking             |
| `io-util`         | I/O utilities                  |
| `sync`            | Synchronization primitives     |
| `macros`          | #[tokio::main], #[tokio::test] |
| `full`            | ทุกอย่าง                       |

---

## Basic Usage

```rust,ignore
#[tokio::main]
async fn main() {
    println!("Hello from Tokio!");

    let result = do_something().await;
    println!("Result: {}", result);
}

async fn do_something() -> String {
    // simulate async work
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    "Done!".to_string()
}

#[tokio::main] ทำอะไร?

// มันแปลง:
#[tokio::main]
async fn main() {
    // code
}

// เป็น:
fn main() {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async {
            // code
        })
}

Manual Runtime

fn main() {
    // สร้าง runtime เอง
    let rt = tokio::runtime::Runtime::new().unwrap();

    // รัน future
    rt.block_on(async {
        println!("Running on Tokio!");
    });

    // หรือ spawn และ block
    let handle = rt.spawn(async {
        42
    });

    let result = rt.block_on(handle).unwrap();
    println!("Result: {}", result);
}

Runtime Types

// Multi-threaded (default)
let rt = tokio::runtime::Builder::new_multi_thread()
    .worker_threads(4)
    .enable_all()
    .build()
    .unwrap();

// Single-threaded (current thread)
let rt = tokio::runtime::Builder::new_current_thread()
    .enable_all()
    .build()
    .unwrap();

Spawning Tasks

tokio::spawn

use tokio::task;

#[tokio::main]
async fn main() {
    // Spawn task - รันแบบ concurrent
    let handle = task::spawn(async {
        println!("Hello from spawned task!");
        42
    });

    // รอผลลัพธ์
    let result = handle.await.unwrap();
    println!("Task returned: {}", result);
}

Multiple Tasks

use tokio::task;

#[tokio::main]
async fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = task::spawn(async move {
            println!("Task {} running", i);
            i * 2
        });
        handles.push(handle);
    }

    // รอทุก tasks
    for handle in handles {
        let result = handle.await.unwrap();
        println!("Result: {}", result);
    }
}

Concurrent Execution

join! - รอทุก futures

use tokio::join;
use tokio::time::{sleep, Duration};

async fn task_a() -> i32 {
    sleep(Duration::from_millis(100)).await;
    1
}

async fn task_b() -> i32 {
    sleep(Duration::from_millis(200)).await;
    2
}

#[tokio::main]
async fn main() {
    // รันพร้อมกัน, รอทุกอันเสร็จ
    let (a, b) = join!(task_a(), task_b());

    println!("Results: {} + {} = {}", a, b, a + b);
    // ใช้เวลา ~200ms (ไม่ใช่ 300ms)
}

try_join! - หยุดเมื่อ error

use tokio::try_join;

async fn may_fail(succeed: bool) -> Result<i32, &'static str> {
    if succeed {
        Ok(42)
    } else {
        Err("failed!")
    }
}

#[tokio::main]
async fn main() {
    let result = try_join!(
        may_fail(true),
        may_fail(true)
    );

    match result {
        Ok((a, b)) => println!("Both succeeded: {}, {}", a, b),
        Err(e) => println!("One failed: {}", e),
    }
}

Tokio Time

use tokio::time::{sleep, interval, Duration, Instant};

#[tokio::main]
async fn main() {
    // Sleep
    println!("Sleeping...");
    sleep(Duration::from_secs(1)).await;
    println!("Woke up!");

    // Interval
    let mut interval = interval(Duration::from_millis(500));
    for _ in 0..3 {
        interval.tick().await;
        println!("Tick!");
    }

    // Measure time
    let start = Instant::now();
    sleep(Duration::from_millis(100)).await;
    println!("Elapsed: {:?}", start.elapsed());
}

Tokio Channels

mpsc - Multiple Producer Single Consumer

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);  // buffer size 32

    // Spawn sender
    let tx1 = tx.clone();
    tokio::spawn(async move {
        tx1.send("Hello from task 1").await.unwrap();
    });

    tokio::spawn(async move {
        tx.send("Hello from task 2").await.unwrap();
    });

    // Receive
    while let Some(msg) = rx.recv().await {
        println!("Received: {}", msg);
    }
}

oneshot - Single value

use tokio::sync::oneshot;

#[tokio::main]
async fn main() {
    let (tx, rx) = oneshot::channel();

    tokio::spawn(async move {
        tx.send("Result!").unwrap();
    });

    let result = rx.await.unwrap();
    println!("Got: {}", result);
}

ลองทำดู! 🎯

  1. สร้าง 3 tasks ด้วย tokio::spawn และรอทุก task
  2. ใช้ interval พิมพ์ทุก 1 วินาที
  3. ใช้ mpsc channel ส่งข้อมูลระหว่าง tasks

สรุป

FunctionDescription
#[tokio::main]Async main entry
tokio::spawnSpawn task
join!Wait for all
try_join!Wait, fail fast
sleepAsync delay
intervalPeriodic timer
mpsc::channelMulti-producer channel
oneshot::channelSingle value channel

👉 ต่อไป: Async Patterns

Async Patterns

รูปแบบการเขียน async code ที่พบบ่อย

select! - Racing Futures

รอหลาย futures พร้อมกัน ใช้อันแรกที่เสร็จ:

use tokio::select;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    select! {
        _ = sleep(Duration::from_secs(1)) => {
            println!("1 second elapsed");
        }
        _ = sleep(Duration::from_secs(2)) => {
            println!("2 seconds elapsed");
        }
    }
    // จะ print "1 second elapsed" เพราะเสร็จก่อน
}

select! กับ Return Values

use tokio::select;
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    let (tx1, mut rx1) = mpsc::channel::<i32>(1);
    let (tx2, mut rx2) = mpsc::channel::<String>(1);

    // Spawn senders
    tokio::spawn(async move {
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        tx1.send(42).await.unwrap();
    });

    tokio::spawn(async move {
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
        tx2.send("hello".to_string()).await.unwrap();
    });

    select! {
        Some(val) = rx1.recv() => println!("Got number: {}", val),
        Some(val) = rx2.recv() => println!("Got string: {}", val),
    }
    // จะได้ string เพราะ 50ms < 100ms
}

Timeout

ให้ future มีเวลาจำกัด:

use tokio::time::{timeout, Duration};

async fn slow_function() -> String {
    tokio::time::sleep(Duration::from_secs(10)).await;
    "Done!".to_string()
}

#[tokio::main]
async fn main() {
    let result = timeout(
        Duration::from_secs(2),  // timeout หลัง 2 วินาที
        slow_function()
    ).await;

    match result {
        Ok(value) => println!("Got: {}", value),
        Err(_) => println!("Timeout! Function took too long."),
    }
}

Timeout กับ select!

use tokio::select;
use tokio::time::{sleep, Duration};

async fn fetch_data() -> String {
    sleep(Duration::from_secs(5)).await;
    "Data".to_string()
}

#[tokio::main]
async fn main() {
    select! {
        result = fetch_data() => {
            println!("Got: {}", result);
        }
        _ = sleep(Duration::from_secs(2)) => {
            println!("Timeout!");
        }
    }
}

Retry Pattern

ลองใหม่เมื่อ fail:

use std::sync::atomic::{AtomicU32, Ordering};

async fn fallible_operation() -> Result<String, &'static str> {
    // Simulate random failure
    static ATTEMPT: AtomicU32 = AtomicU32::new(0);
    
    // fetch_add returns the previous value
    let count = ATTEMPT.fetch_add(1, Ordering::SeqCst);
    
    if count < 3 {
        Err("failed")
    } else {
        Ok("success!".to_string())
    }
}

async fn retry<F, Fut, T, E>(f: F, max_retries: u32) -> Result<T, E>
where
    F: Fn() -> Fut,
    Fut: std::future::Future<Output = Result<T, E>>,
{
    let mut attempt = 0;
    loop {
        match f().await {
            Ok(value) => return Ok(value),
            Err(e) => {
                attempt += 1;
                if attempt >= max_retries {
                    return Err(e);
                }
                println!("Retry attempt {}/{}", attempt, max_retries);
                sleep(Duration::from_millis(100 * attempt as u64)).await;
            }
        }
    }
}

#[tokio::main]
async fn main() {
    let result = retry(|| fallible_operation(), 5).await;
    println!("Result: {:?}", result);
}

Graceful Shutdown

ปิดโปรแกรมอย่างสะอาด:

use tokio::signal;
use tokio::sync::broadcast;

#[tokio::main]
async fn main() {
    // สร้าง shutdown signal
    let (shutdown_tx, _) = broadcast::channel::<()>(1);

    // Spawn workers
    for i in 0..3 {
        let mut rx = shutdown_tx.subscribe();
        tokio::spawn(async move {
            loop {
                tokio::select! {
                    _ = rx.recv() => {
                        println!("Worker {} shutting down", i);
                        break;
                    }
                    _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {
                        println!("Worker {} working...", i);
                    }
                }
            }
        });
    }

    // รอ Ctrl+C
    signal::ctrl_c().await.unwrap();
    println!("Shutdown signal received!");

    // ส่ง shutdown
    drop(shutdown_tx);

    // รอให้ workers เสร็จ
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    println!("Goodbye!");
}

Rate Limiting

จำกัดความถี่:

use tokio::time::{interval, Duration};
use tokio::sync::Semaphore;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    // อนุญาตแค่ 3 concurrent requests
    let semaphore = Arc::new(Semaphore::new(3));

    let mut handles = vec![];

    for i in 0..10 {
        let permit = semaphore.clone().acquire_owned().await.unwrap();

        let handle = tokio::spawn(async move {
            println!("Request {} started", i);
            tokio::time::sleep(Duration::from_millis(500)).await;
            println!("Request {} done", i);
            drop(permit);  // release permit
        });

        handles.push(handle);
    }

    for handle in handles {
        handle.await.unwrap();
    }
}

Buffering and Batching

รวม items ก่อน process:

use tokio::sync::mpsc;
use tokio::time::{timeout, Duration};

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel::<i32>(100);

    // Producer
    tokio::spawn(async move {
        for i in 0..20 {
            tx.send(i).await.unwrap();
            tokio::time::sleep(Duration::from_millis(50)).await;
        }
    });

    // Consumer with batching
    let batch_size = 5;
    let batch_timeout = Duration::from_millis(200);

    loop {
        let mut batch = Vec::with_capacity(batch_size);

        // Collect batch
        loop {
            let result = timeout(batch_timeout, rx.recv()).await;

            match result {
                Ok(Some(item)) => {
                    batch.push(item);
                    if batch.len() >= batch_size {
                        break;
                    }
                }
                Ok(None) => break,  // channel closed
                Err(_) => break,     // timeout
            }
        }

        if batch.is_empty() {
            break;
        }

        println!("Processing batch: {:?}", batch);
    }
}

ลองทำดู! 🎯

  1. ใช้ select! เลือกระหว่าง 2 async operations
  2. สร้าง function ที่มี timeout
  3. implement retry logic สำหรับ API call

สรุปบทที่ 16

PatternUse Case
select!Race futures
timeoutLimit execution time
RetryHandle transient failures
SemaphoreRate limiting
BatchingEfficient processing
Graceful shutdownClean exit

เปรียบเทียบ

MacroBehavior
join!รอทุกอันเสร็จ
select!ใช้อันแรกที่เสร็จ
try_join!รอทุกอัน หยุดเมื่อ error

👉 ต่อไป: บทที่ 17: Unsafe Rust

บทที่ 17: Unsafe Rust

unsafe ช่วยให้ทำสิ่งที่ compiler ตรวจสอบไม่ได้

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Unsafe คืออะไรเมื่อไหร่ควรใช้
Raw PointersPointers แบบ C
Unsafe FunctionsFFI และ extern
Safe Abstractionsห่อ unsafe ด้วย safe API

👉 Unsafe คืออะไร

Unsafe คืออะไร

unsafe ปลดล็อคความสามารถพิเศษที่ compiler ตรวจสอบไม่ได้

🚨 อันตราย!

unsafe หมายความว่าคุณ รับผิดชอบ ความถูกต้องแทน compiler:

  • ❌ Memory corruption
  • ❌ Data races
  • ❌ Undefined behavior
  • ❌ Security vulnerabilities

ใช้ unsafe เมื่อจำเป็นจริงๆ เท่านั้น!

Unsafe Superpowers

ใน unsafe block ทำได้ 5 อย่าง:

Superpowerคำอธิบาย
Dereference raw pointersใช้ *const T, *mut T
Call unsafe functionsฟังก์ชันที่มี unsafe
Access mutable staticsตัวแปร static mut
Implement unsafe traitsเช่น Send, Sync
Access union fieldsunion แบบ C

🔒 Safe vs Unsafe Visualization

+-------------------------------------------------------------------+
|                         Rust's Safety Layers                      |
+-------------------------------------------------------------------+
|                                                                   |
|   +-----------------------------------------------------------+   |
|   |                    SAFE RUST (99% of code)                |   |
|   |  +---------------------------------------------------------+  |
|   |  |  * Ownership rules                                     |   |
|   |  |  * Borrow checker                                      |   |
|   |  |  * Type safety                                         |   |
|   |  |  * Memory safety                                       |   |
|   |  +---------------------------------------------------------+  |
|   +-----------------------------------------------------------+   |
|                              |                                    |
|                              v                                    |
|   +-----------------------------------------------------------+   |
|   |                 UNSAFE BLOCK (1% - when needed)           |   |
|   |  +---------------------------------------------------------+  |
|   |  |  * Raw pointers                                        |   |
|   |  |  * FFI (Foreign Function Interface)                    |   |
|   |  |  * Mutable statics                                     |   |
|   |  |  * You are responsible for correctness!                |   |
|   |  +---------------------------------------------------------+  |
|   +-----------------------------------------------------------+   |
|                                                                   |
+-------------------------------------------------------------------+

🌍 Real-World Use Cases

Use Caseตัวอย่างทำไมต้อง Unsafe
FFIเรียก C libraries (OpenSSL, SQLite)C ไม่มี safety guarantees
Hardwareเข้าถึง memory-mapped I/Oต้อง raw pointer
PerformanceCustom allocators, SIMDควบคุม memory เอง
OS APIsSystem callsLow-level interface

💡 ตัวอย่างจริง: Vec<T> และ String implement ด้วย unsafe ภายใน แต่ให้ safe API ออกมา


1. Dereference Raw Pointers

fn main() {
    let mut num = 5;

    // สร้าง raw pointers (safe - ยังไม่ได้ dereference)
    let r1 = &num as *const i32;  // immutable raw
    let r2 = &mut num as *mut i32; // mutable raw

    // dereference ต้อง unsafe
    unsafe {
        println!("r1 is: {}", *r1);
        *r2 = 10;
        println!("r2 is: {}", *r2);
    }
}

Raw Pointers vs References

AspectReferencesRaw Pointers
Nullไม่ได้ได้
DanglingCompiler ป้องกันไม่ป้องกัน
Aliasingมี rulesไม่มี rules
Auto-cleanupผ่าน Dropไม่มี

2. Call Unsafe Functions

unsafe fn dangerous() {
    println!("Doing dangerous stuff!");
}

fn main() {
    // เรียก unsafe function ต้องอยู่ใน unsafe block
    unsafe {
        dangerous();
    }
}

3. Access Mutable Statics

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

คำเตือน: static mut อันตรายมากใน multi-threaded ใช้ Mutex หรือ Atomic แทน


4. Implement Unsafe Traits

#![allow(unused)]
fn main() {
unsafe trait MyUnsafeTrait {
    fn do_something(&self);
}

unsafe impl MyUnsafeTrait for i32 {
    fn do_something(&self) {
        println!("{}", self);
    }
}
}

ตัวอย่างจริง: Send และ Sync traits


5. Access Union Fields

#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}

fn main() {
    let u = MyUnion { f1: 1 };

    // อ่าน field ต้อง unsafe เพราะไม่รู้ว่า field ไหนถูก set
    unsafe {
        println!("f1: {}", u.f1);
    }
}

เมื่อไหร่ควรใช้ unsafe?

✅ ใช้เมื่อ

  1. FFI - เรียก C/C++ code
  2. Performance - hot paths ที่ต้องเร็วมาก
  3. Hardware - เข้าถึง hardware โดยตรง
  4. Implement abstractions - สร้าง safe wrapper

❌ ไม่ควรใช้

  1. ข้าม borrow checker เพราะไม่เข้าใจ
  2. แก้ compile error แบบขี้เกียจ
  3. ทุกที่ที่มีทางเลือก safe

FFI: เรียก C Code

ตัวอย่างเรียก function จาก C library:

// ประกาศ external C function
extern "C" {
    fn abs(input: i32) -> i32;
    fn sqrt(input: f64) -> f64;
}

fn main() {
    unsafe {
        println!("abs(-5) = {}", abs(-5));      // 5
        println!("sqrt(9.0) = {}", sqrt(9.0));  // 3.0
    }
}

สร้าง Rust function ที่ C เรียกได้

// export function สำหรับ C
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

Safe Abstractions

วิธีที่ถูกต้อง: ห่อ unsafe ด้วย safe interface

#![allow(unused)]
fn main() {
pub fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    // unsafe ภายใน แต่ interface เป็น safe
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}
}

ลองทำดู! 🎯

  1. สร้าง raw pointer และ dereference
  2. สร้าง unsafe function
  3. ลองเข้าถึง static mut

🌍 Real-World Example: FFI เรียก C Function

ตัวอย่างเรียก strlen จาก C standard library:

use std::ffi::CString;

// ประกาศ external C function
extern "C" {
    fn strlen(s: *const i8) -> usize;
}

fn safe_strlen(s: &str) -> usize {
    // แปลง Rust string เป็น C string
    let c_string = CString::new(s).expect("CString failed");
    
    // เรียก C function ใน unsafe block
    unsafe {
        strlen(c_string.as_ptr())
    }
}

fn main() {
    let text = "Hello, Rust!";
    let len = safe_strlen(text);
    println!("Length: {}", len);  // Length: 12
}

FFI Pattern

+-------------------------------------------------------------------+
|                    Safe Wrapper Pattern                           |
+-------------------------------------------------------------------+
|                                                                   |
|   User Code (Safe)                                                |
|        |                                                          |
|        v                                                          |
|   +--------------------------------------------+                  |
|   |  safe_strlen(s: &str) -> usize             | <--- Safe API    |
|   |    * Validate input                        |                  |
|   |    * Convert to C types                    |                  |
|   |    * Call unsafe code                      |                  |
|   |    * Convert result back                   |                  |
|   +--------------------------------------------+                  |
|        |                                                          |
|        v                                                          |
|   +--------------------------------------------+                  |
|   |  unsafe { strlen(ptr) }                    | <--- Unsafe FFI  |
|   +--------------------------------------------+                  |
|        |                                                          |
|        v                                                          |
|   C Library (libc)                                                |
|                                                                   |
+-------------------------------------------------------------------+

สรุป

Keywordใช้เมื่อ
unsafe { }Block ที่ทำ unsafe operations
unsafe fnFunction ที่ต้องเรียกใน unsafe
unsafe traitTrait ที่ต้อง impl ใน unsafe
unsafe implImplement unsafe trait

Best Practices

  1. ลด unsafe ให้น้อยที่สุด
  2. Document invariants ที่ต้องรักษา
  3. Test extensively
  4. Wrap ด้วย safe API

👉 ต่อไป: Raw Pointers

Raw Pointers

Raw Pointers (*const T และ *mut T) คือ pointers แบบ C ไม่มี safety guarantees

สร้าง Raw Pointers

fn main() {
    let mut num = 5;

    // สร้าง raw pointers จาก references (safe)
    let r1: *const i32 = &num;      // immutable raw pointer
    let r2: *mut i32 = &mut num;    // mutable raw pointer

    println!("r1 address: {:p}", r1);
    println!("r2 address: {:p}", r2);

    // การสร้าง pointer เป็น safe
    // แต่การใช้งาน (dereference) ต้อง unsafe
}

Dereference Raw Pointers

fn main() {
    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // ❌ Error: ทำนอก unsafe ไม่ได้
    // println!("{}", *r1);

    // ✅ ต้องใช้ unsafe block
    unsafe {
        println!("r1: {}", *r1);  // อ่านค่า
        *r2 = 10;                  // เขียนค่า
        println!("r2: {}", *r2);
    }
}

Raw Pointers vs References

AspectReferences (&T)Raw Pointers (*const T)
Null❌ ไม่ได้✅ ได้
Dangling❌ Compiler ป้องกัน✅ อาจเกิดได้
Aliasingมี rulesไม่มี rules
Auto-deref✅ ได้❌ ต้อง unsafe
Lifetime✅ tracked❌ ไม่ tracked

สร้าง Null Pointer

fn main() {
    // null pointer
    let null_ptr: *const i32 = std::ptr::null();
    let null_mut_ptr: *mut i32 = std::ptr::null_mut();

    // ตรวจสอบ null
    if null_ptr.is_null() {
        println!("Pointer is null!");
    }

    // ❌ อันตราย: dereference null pointer จะ crash
    // unsafe { println!("{}", *null_ptr); }
}

Pointer Arithmetic

fn main() {
    let arr = [1, 2, 3, 4, 5];
    let ptr = arr.as_ptr();  // *const i32

    unsafe {
        // เข้าถึง elements ด้วย pointer arithmetic
        println!("arr[0] = {}", *ptr);
        println!("arr[1] = {}", *ptr.add(1));
        println!("arr[2] = {}", *ptr.add(2));

        // offset ใช้ signed integer
        println!("arr[4] = {}", *ptr.offset(4));
    }
}

Pointer Methods

MethodDescription
ptr.add(n)เลื่อนไปข้างหน้า n elements
ptr.sub(n)เลื่อนไปข้างหลัง n elements
ptr.offset(n)เลื่อนด้วย signed offset
ptr.is_null()ตรวจ null
ptr.read()อ่านค่า (unsafe)
ptr.write(v)เขียนค่า (unsafe)

Casting Between Pointer Types

fn main() {
    let mut num = 42i32;

    // reference -> raw pointer
    let ptr: *mut i32 = &mut num;

    // const <-> mut (อันตราย!)
    let const_ptr: *const i32 = ptr;
    let mut_ptr: *mut i32 = const_ptr as *mut i32;

    // pointer -> integer
    let addr: usize = ptr as usize;
    println!("Address: 0x{:x}", addr);

    // integer -> pointer (อันตรายมาก!)
    let ptr_from_int: *const i32 = addr as *const i32;

    unsafe {
        println!("Value: {}", *ptr_from_int);
    }
}

ตัวอย่างจริง: Swap Values

unsafe fn swap<T>(a: *mut T, b: *mut T) {
    let temp = std::ptr::read(a);
    std::ptr::copy_nonoverlapping(b, a, 1);
    std::ptr::write(b, temp);
}

fn main() {
    let mut x = 1;
    let mut y = 2;

    unsafe {
        swap(&mut x, &mut y);
    }

    println!("x = {}, y = {}", x, y);  // x = 2, y = 1
}

Safe Pointer Functions

use std::ptr;

fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    let src = [10, 20, 30];

    unsafe {
        // copy_nonoverlapping: memcpy
        ptr::copy_nonoverlapping(
            src.as_ptr(),
            arr.as_mut_ptr(),
            3
        );
    }

    println!("{:?}", arr);  // [10, 20, 30, 4, 5]
}

ลองทำดู! 🎯

  1. สร้าง raw pointer จาก reference
  2. ใช้ pointer arithmetic เข้าถึง array
  3. ลอง swap values ด้วย raw pointers

สรุป

ConceptSyntax
Immutable*const T
Mutable*mut T
Create&x as *const T
Nullstd::ptr::null()
Dereferenceunsafe { *ptr }
Addptr.add(n)

ข้อควรระวัง

  1. ❌ อย่า dereference null pointer
  2. ❌ อย่า dereference dangling pointer
  3. ❌ อย่าละเมิด aliasing rules
  4. ✅ ใช้เฉพาะเมื่อจำเป็น

👉 ต่อไป: Unsafe Functions

Unsafe Functions

ฟังก์ชันที่ต้องเรียกภายใน unsafe block

Defining Unsafe Functions

// ประกาศด้วย unsafe fn
unsafe fn dangerous() {
    println!("Doing something dangerous!");
}

fn main() {
    // ❌ Error: เรียกนอก unsafe ไม่ได้
    // dangerous();

    // ✅ ต้องเรียกใน unsafe block
    unsafe {
        dangerous();
    }
}

เมื่อไหร่ใช้ unsafe fn?

ใช้เมื่อฟังก์ชันมี preconditions ที่ compiler ตรวจไม่ได้:

/// Divides two numbers without checking for zero.
///
/// # Safety
///
/// Caller must ensure that `divisor` is not zero.
unsafe fn divide_unchecked(dividend: i32, divisor: i32) -> i32 {
    dividend / divisor
}

fn main() {
    let a = 10;
    let b = 2;

    // Caller รับผิดชอบเรื่อง preconditions
    unsafe {
        let result = divide_unchecked(a, b);
        println!("{} / {} = {}", a, b, result);
    }
}

Document Safety Requirements

สำคัญ: ทุก unsafe fn ควรมี # Safety section:

#![allow(unused)]
fn main() {
/// Reads a value from the given pointer.
///
/// # Safety
///
/// - `ptr` must be valid and properly aligned.
/// - `ptr` must point to a properly initialized value of type `T`.
/// - The memory at `ptr` must not be mutated while this function runs.
unsafe fn read_value<T>(ptr: *const T) -> T {
    ptr.read()
}
}

FFI - Foreign Function Interface

เรียกฟังก์ชันจากภาษาอื่น (เช่น C):

เรียก C Functions

// ประกาศ external functions
extern "C" {
    fn abs(input: i32) -> i32;
    fn sqrt(x: f64) -> f64;
}

fn main() {
    unsafe {
        println!("abs(-5) = {}", abs(-5));
        println!("sqrt(16.0) = {}", sqrt(16.0));
    }
}

Calling Convention

ABIใช้กับ
"C"C functions (default)
"system"Windows API
"stdcall"Win32 API
"fastcall"Optimized
extern "system" {
    fn GetCurrentProcessId() -> u32;
}

Export Rust to C

#[no_mangle]  // ไม่เปลี่ยนชื่อ function
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn greet(name: *const std::ffi::c_char) {
    unsafe {
        let c_str = std::ffi::CStr::from_ptr(name);
        if let Ok(s) = c_str.to_str() {
            println!("Hello, {}!", s);
        }
    }
}

Build as Library

# Cargo.toml
[lib]
crate-type = ["cdylib"]  # Dynamic library
# หรือ
crate-type = ["staticlib"]  # Static library

Working with C Strings

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    // Rust String -> C string
    let rust_string = "Hello, C!";
    let c_string = CString::new(rust_string).unwrap();

    unsafe {
        let len = strlen(c_string.as_ptr());
        println!("Length: {}", len);
    }

    // C string -> Rust String
    let c_ptr: *const c_char = c_string.as_ptr();
    unsafe {
        let c_str = CStr::from_ptr(c_ptr);
        let rust_str = c_str.to_str().unwrap();
        println!("Back to Rust: {}", rust_str);
    }
}

ตัวอย่างจริง: Binding to C Library

// Binding to libc
#[link(name = "c")]
extern "C" {
    fn puts(s: *const std::ffi::c_char) -> i32;
    fn getenv(name: *const std::ffi::c_char) -> *const std::ffi::c_char;
}

fn main() {
    use std::ffi::{CString, CStr};

    let msg = CString::new("Hello from Rust via C!").unwrap();
    let var = CString::new("PATH").unwrap();

    unsafe {
        puts(msg.as_ptr());

        let path = getenv(var.as_ptr());
        if !path.is_null() {
            let path_str = CStr::from_ptr(path).to_str().unwrap();
            println!("PATH = {}", &path_str[..50.min(path_str.len())]);
        }
    }
}

Static Mut Variables

static mut COUNTER: u32 = 0;

fn increment() {
    unsafe {
        COUNTER += 1;
    }
}

fn get_count() -> u32 {
    unsafe { COUNTER }
}

fn main() {
    increment();
    increment();
    increment();

    println!("Count: {}", get_count());  // 3
}

คำเตือน: static mut อันตรายใน multi-threaded! ใช้ Mutex หรือ AtomicU32 แทน


ลองทำดู! 🎯

  1. สร้าง unsafe fn พร้อม Safety documentation
  2. เรียก C function abs และ sqrt
  3. สร้าง Rust function ที่ export ไป C

สรุป

ConceptSyntax
Declareunsafe fn name() {}
Callunsafe { name(); }
FFI declareextern "C" { fn name(); }
FFI export#[no_mangle] pub extern "C" fn
C stringCString::new(), CStr::from_ptr()
Static mutstatic mut NAME: T = value;

👉 ต่อไป: Safe Abstractions

Safe Abstractions

ห่อ unsafe code ด้วย safe API - วิธีที่ถูกต้องในการใช้ unsafe

หลักการ

Unsafe ควรอยู่ข้างใน, Safe ควรอยู่ข้างนอก

+-------------------------------+
|           Safe API            |  <--- ผู้ใช้เรียกที่นี่
|  +-------------------------+  |
|  |   Unsafe Implementation |  |  <--- unsafe อยู่ข้างใน
|  |        (hidden)         |  |
|  +-------------------------+  |
+-------------------------------+

ตัวอย่าง 1: split_at_mut

Standard library ใช้ pattern นี้:

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    // Safe precondition check
    assert!(mid <= len, "mid must be <= len");

    // Unsafe implementation ที่ถูกต้อง
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut v, 3);

    left[0] = 100;
    right[0] = 200;

    println!("{:?}", left);   // [100, 2, 3]
    println!("{:?}", right);  // [200, 5, 6]
}

ทำไมถึง Safe?

  1. Precondition check: assert!(mid <= len) ป้องกัน out-of-bounds
  2. Non-overlapping: สอง slices ไม่ทับกัน
  3. Valid lifetimes: ทั้งคู่ผูกกับ input lifetime

ตัวอย่าง 2: Custom Vec

pub struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
}

impl<T> MyVec<T> {
    pub fn new() -> Self {
        MyVec {
            ptr: std::ptr::null_mut(),
            len: 0,
            cap: 0,
        }
    }

    pub fn push(&mut self, value: T) {
        if self.len == self.cap {
            self.grow();
        }

        unsafe {
            std::ptr::write(self.ptr.add(self.len), value);
        }
        self.len += 1;
    }

    pub fn get(&self, index: usize) -> Option<&T> {
        if index < self.len {
            unsafe { Some(&*self.ptr.add(index)) }
        } else {
            None
        }
    }

    fn grow(&mut self) {
        let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 };
        let new_layout = std::alloc::Layout::array::<T>(new_cap).unwrap();

        let new_ptr = if self.cap == 0 {
            unsafe { std::alloc::alloc(new_layout) as *mut T }
        } else {
            let old_layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
            unsafe {
                std::alloc::realloc(
                    self.ptr as *mut u8,
                    old_layout,
                    new_layout.size(),
                ) as *mut T
            }
        };

        self.ptr = new_ptr;
        self.cap = new_cap;
    }
}

impl<T> Drop for MyVec<T> {
    fn drop(&mut self) {
        if self.cap > 0 {
            // Drop all elements
            for i in 0..self.len {
                unsafe {
                    std::ptr::drop_in_place(self.ptr.add(i));
                }
            }
            // Free memory
            let layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
            unsafe {
                std::alloc::dealloc(self.ptr as *mut u8, layout);
            }
        }
    }
}

ตัวอย่าง 3: Thread-safe Counter

#![allow(unused)]
fn main() {
use std::sync::atomic::{AtomicUsize, Ordering};

pub struct Counter {
    value: AtomicUsize,
}

impl Counter {
    pub fn new() -> Self {
        Counter {
            value: AtomicUsize::new(0),
        }
    }

    pub fn increment(&self) -> usize {
        self.value.fetch_add(1, Ordering::SeqCst)
    }

    pub fn get(&self) -> usize {
        self.value.load(Ordering::SeqCst)
    }
}

// Safe to share across threads
unsafe impl Sync for Counter {}
}

Best Practices

1. Minimize Unsafe Scope

// ❌ Bad: unsafe block ใหญ่เกินไป
unsafe {
    let ptr = some_pointer();
    let len = calculate_length();
    validate_input(len);
    let slice = std::slice::from_raw_parts(ptr, len);
    process(slice);
}

// ✅ Good: unsafe แค่ที่จำเป็น
let ptr = some_pointer();
let len = calculate_length();
validate_input(len);
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
process(slice);

2. Document Invariants

/// A non-empty string that is guaranteed to be valid UTF-8.
///
/// # Invariants
///
/// - `ptr` always points to valid memory
/// - The memory contains valid UTF-8 bytes
/// - `len` is always > 0
pub struct NonEmptyString {
    ptr: *const u8,
    len: usize,
}

3. Validate Early

pub fn from_raw_parts(ptr: *const u8, len: usize) -> Result<Self, Error> {
    // ตรวจสอบก่อน unsafe
    if ptr.is_null() {
        return Err(Error::NullPointer);
    }
    if len == 0 {
        return Err(Error::EmptyString);
    }

    // unsafe เฉพาะส่วนที่ต้องใช้จริง
    let slice = unsafe { std::slice::from_raw_parts(ptr, len) };

    // ตรวจสอบหลัง
    if std::str::from_utf8(slice).is_err() {
        return Err(Error::InvalidUtf8);
    }

    Ok(Self { ptr, len })
}

4. Test Extensively

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_split_at_mut_basic() {
        let mut v = vec![1, 2, 3, 4];
        let (left, right) = split_at_mut(&mut v, 2);
        assert_eq!(left, &[1, 2]);
        assert_eq!(right, &[3, 4]);
    }

    #[test]
    fn test_split_at_mut_empty_left() {
        let mut v = vec![1, 2, 3];
        let (left, right) = split_at_mut(&mut v, 0);
        assert!(left.is_empty());
        assert_eq!(right, &[1, 2, 3]);
    }

    #[test]
    #[should_panic]
    fn test_split_at_mut_out_of_bounds() {
        let mut v = vec![1, 2, 3];
        split_at_mut(&mut v, 10);  // should panic
    }
}
}

ลองทำดู! 🎯

  1. สร้าง safe wrapper สำหรับ raw pointer operation
  2. implement get_unchecked ใน custom collection
  3. เขียน tests สำหรับ edge cases

สรุปบทที่ 17

แนวคิดคำอธิบาย
unsafe blockเปิดใช้ unsafe features
raw pointers*const T, *mut T
unsafe fnฟังก์ชันที่ต้อง unsafe
externFFI
safe abstractionห่อ unsafe ด้วย safe API

Checklist ก่อน Ship unsafe Code

  • Preconditions ถูก document
  • ทุก invariant ถูกรักษา
  • unsafe scope น้อยที่สุด
  • มี tests ครอบคลุม
  • Code review แล้ว

👉 ต่อไป: บทที่ 18: Macros

บทที่ 18: Macros

Macros สร้าง code ตอน compile time

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Declarativemacro_rules!
Proceduralderive, attribute
Common Macrosprintln!, vec!, format!

👉 Declarative Macros

Declarative Macros

Declarative macros หรือ macro_rules! สร้าง code patterns ที่ซ้ำๆ

ทำไมต้องใช้ Macros?

// ❌ ซ้ำซาก
let v1 = vec![1, 2, 3];
let v2 = {
    let mut temp = Vec::new();
    temp.push(1);
    temp.push(2);
    temp.push(3);
    temp
};

// ✅ ใช้ macro - สั้นกว่า
let v1 = vec![1, 2, 3];

🔄 Macro Expansion Visualization

+-------------------------------------------------------------------+
|                    Macro Expansion Process                        |
+-------------------------------------------------------------------+
|                                                                   |
|   Source Code (you write)           Expanded Code (Compiler sees) |
|   -------------------         -->   --------------------------    |
|                                                                   |
|   vec![1, 2, 3]                    {                              |
|         |                              let mut temp = Vec::new(); |
|         |                              temp.push(1);              |
|         |                              temp.push(2);              |
|         |                              temp.push(3);              |
|         |                              temp                       |
|         +------------------------> }                              |
|                                                                   |
|   println!("x = {}", x)            std::io::_print(               |
|         |                              format_args!("x = {}", x)  |
|         +------------------------> )                              |
|                                                                   |
+-------------------------------------------------------------------+

📋 Macro Cheatsheet

Patternความหมายตัวอย่าง
$x:exprExpression ใดๆ1 + 2, foo()
$x:identIdentifiermy_var, foo
$x:tyTypei32, String
$($x:expr),*Repeat 0+ ครั้ง1, 2, 3
$($x:expr),+Repeat 1+ ครั้งต้องมีอย่างน้อย 1
$($x:expr);*Repeat คั่นด้วย ;a; b; c

Syntax พื้นฐาน

macro_rules! macro_name {
    ( pattern ) => {
        expansion
    };
}

ตัวอย่าง 1: Simple Macro

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

fn main() {
    say_hello!();  // Hello!
}

ตัวอย่าง 2: With Arguments

macro_rules! greet {
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
}

fn main() {
    greet!("World");   // Hello, World!
    greet!("Rust");    // Hello, Rust!
    greet!(1 + 2);     // Hello, 3!
}

Designators (ตัวจับ)

DesignatorMatches
exprExpression เช่น 1 + 2, x
identIdentifier เช่น foo, x
tyType เช่น i32, String
patPattern เช่น Some(x), _
blockBlock { ... }
stmtStatement
literalLiteral เช่น "hello", 42
ttToken tree (ทุกอย่าง)

ตัวอย่าง 3: Multiple Patterns

macro_rules! calculate {
    (add $a:expr, $b:expr) => {
        $a + $b
    };
    (sub $a:expr, $b:expr) => {
        $a - $b
    };
    (mul $a:expr, $b:expr) => {
        $a * $b
    };
}

fn main() {
    println!("{}", calculate!(add 1, 2));  // 3
    println!("{}", calculate!(sub 5, 3));  // 2
    println!("{}", calculate!(mul 4, 5));  // 20
}

ตัวอย่าง 4: Repetition

ใช้ $(...)* หรือ $(...)+ สำหรับซ้ำ:

macro_rules! vec_of_strings {
    // ( pattern ),* = zero or more, comma separated
    ($($x:expr),*) => {
        {
            let mut temp = Vec::new();
            $(
                temp.push($x.to_string());
            )*
            temp
        }
    };
}

fn main() {
    let v = vec_of_strings!["a", "b", "c"];
    println!("{:?}", v);  // ["a", "b", "c"]

    let v2 = vec_of_strings![1, 2, 3];
    println!("{:?}", v2); // ["1", "2", "3"]
}

Repetition Operators

OperatorMeaning
*Zero or more
+One or more
?Zero or one

ตัวอย่าง 5: สร้าง HashMap

macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {
        {
            let mut map = std::collections::HashMap::new();
            $(
                map.insert($key, $value);
            )*
            map
        }
    };
}

fn main() {
    let scores = hashmap! {
        "Alice" => 100,
        "Bob" => 85,
        "Charlie" => 90,
    };

    println!("{:?}", scores);
}

ตัวอย่าง 6: Debug Macro

macro_rules! debug_var {
    ($var:expr) => {
        println!("{} = {:?}", stringify!($var), $var);
    };
}

fn main() {
    let x = 42;
    let name = "Rust";
    let vec = vec![1, 2, 3];

    debug_var!(x);     // x = 42
    debug_var!(name);  // name = "Rust"
    debug_var!(vec);   // vec = [1, 2, 3]
}

stringify! แปลง expression เป็น string literal


ตัวอย่าง 7: Multiple Types

macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("Called {:?}()", stringify!($func_name));
        }
    };
}

create_function!(foo);
create_function!(bar);

fn main() {
    foo();  // Called "foo"()
    bar();  // Called "bar"()
}

ลองทำดู! 🎯

  1. สร้าง macro min! ที่หาค่าต่ำสุดของ 2 ค่า
  2. สร้าง macro println_all! ที่ print หลายค่า
  3. สร้าง macro ที่สร้าง struct

🌍 Real-World Example: Hashmap Literal Macro

สร้าง HashMap แบบสั้นๆ เหมือน vec!:

macro_rules! hashmap {
    // Empty hashmap
    () => {
        std::collections::HashMap::new()
    };
    
    // With key-value pairs
    ($($key:expr => $value:expr),+ $(,)?) => {{
        let mut map = std::collections::HashMap::new();
        $(
            map.insert($key, $value);
        )+
        map
    }};
}

fn main() {
    // ใช้งาน macro
    let scores = hashmap! {
        "Alice" => 100,
        "Bob" => 85,
        "Charlie" => 92,
    };
    
    println!("{:?}", scores);
    // {"Alice": 100, "Bob": 85, "Charlie": 92}
}

📋 Common Macro Patterns

PatternUse CaseExample
Literalสร้าง collectionvec!, hashmap!
DSLDomain-specific syntaxhtml!, sql!
Code Genสร้าง boilerplate#[derive(...)]
AssertionTestingassert!, assert_eq!

เปรียบเทียบ Macros vs Functions

AspectMacrosFunctions
EvaluationCompile-timeRuntime
Typesไม่ต้องระบุต้องระบุ
ArgumentsVariableFixed
SyntaxFlexibleFixed
Debugยากกว่าง่ายกว่า

สรุป

PatternMeaning
$x:exprMatch expression
$x:identMatch identifier
$($x:expr),*Zero or more exprs
$($x:expr),+One or more exprs
$($x:expr)?Optional expr
stringify!($x)Convert to string

👉 ต่อไป: Procedural Macros

Procedural Macros

Procedural Macros แปลง Rust code เป็น code อื่น ณ compile time

3 ประเภท

TypeSyntaxUse Case
Derive#[derive(MyMacro)]Generate impl blocks
Attribute#[my_attr]Modify items
Function-likemy_macro!(...)Custom syntax

Setup

Procedural macros ต้องอยู่ใน separate crate:

# my-macro/Cargo.toml
[package]
name = "my-macro"
version = "0.1.0"

[lib]
proc-macro = true

[dependencies]
quote = "1"
syn = { version = "2", features = ["full"] }
proc-macro2 = "1"

Derive Macro Example

สร้าง derive macro สำหรับ HelloMacro trait:

1. Define Trait (main crate)

// src/lib.rs
pub trait HelloMacro {
    fn hello_macro();
}

2. Create Derive Macro (macro crate)

// my-macro/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Parse input
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    // Generate code
    let expanded = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello from {}!", stringify!(#name));
            }
        }
    };

    TokenStream::from(expanded)
}

3. Use It

use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();  // "Hello from Pancakes!"
}

Derive with Fields

อ่าน struct fields:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};

#[proc_macro_derive(Describe)]
pub fn describe_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    // Get field names
    let field_names: Vec<_> = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => fields
                .named
                .iter()
                .map(|f| f.ident.as_ref().unwrap().to_string())
                .collect(),
            _ => vec![],
        },
        _ => vec![],
    };

    let fields_str = field_names.join(", ");

    let expanded = quote! {
        impl #name {
            pub fn describe() {
                println!("{} has fields: {}", stringify!(#name), #fields_str);
            }
        }
    };

    TokenStream::from(expanded)
}

Usage:

#[derive(Describe)]
struct User {
    name: String,
    age: u32,
}

fn main() {
    User::describe();  // "User has fields: name, age"
}

Attribute Macro Example

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_call(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let name = &input.sig.ident;
    let block = &input.block;
    let sig = &input.sig;

    let expanded = quote! {
        #sig {
            println!("Calling function: {}", stringify!(#name));
            let result = (|| #block)();
            println!("Function {} returned", stringify!(#name));
            result
        }
    };

    TokenStream::from(expanded)
}

Usage:

#[log_call]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let sum = add(2, 3);
    // Output:
    // Calling function: add
    // Function add returned
    println!("Sum: {}", sum);
}

Function-like Macro

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};

#[proc_macro]
pub fn make_greeting(input: TokenStream) -> TokenStream {
    let name = parse_macro_input!(input as LitStr);
    let greeting = format!("Hello, {}!", name.value());

    let expanded = quote! {
        #greeting
    };

    TokenStream::from(expanded)
}

Usage:

let msg = make_greeting!("World");
println!("{}", msg);  // "Hello, World!"

Real-world Examples

หลาย crates ใช้ procedural macros:

CrateMacroPurpose
serde#[derive(Serialize, Deserialize)]JSON/YAML serialization
tokio#[tokio::main]Async runtime setup
axum#[debug_handler]Better error messages
sqlx#[derive(FromRow)]Database mapping
thiserror#[derive(Error)]Error types

ลองทำดู! 🎯

  1. สร้าง derive macro สำหรับ Debug clone
  2. สร้าง attribute macro สำหรับ timing functions
  3. ศึกษา syn และ quote documentation

สรุป

ConceptDescription
proc_macroRust’s proc macro crate
synParse Rust code
quoteGenerate Rust code
TokenStreamInput/Output of macros

Crate Structure

my-project/
├── Cargo.toml
├── src/lib.rs         # Main library
└── my-macro/
    ├── Cargo.toml     # proc-macro = true
    └── src/lib.rs     # Macro implementations

👉 ต่อไป: Macros ที่ใช้บ่อย

Macros ที่ใช้บ่อย

รวม macros ที่ใช้บ่อยใน Rust standard library

Output Macros

println! และ print!

fn main() {
    // Basic
    println!("Hello, World!");
    print!("No newline");
    println!(" - now with newline");

    // With placeholders
    println!("Value: {}", 42);
    println!("Multiple: {} and {}", 1, 2);

    // Named arguments
    println!("{name} is {age} years old", name = "Alice", age = 30);

    // Debug format
    let v = vec![1, 2, 3];
    println!("{:?}", v);    // [1, 2, 3]
    println!("{:#?}", v);   // Pretty print

    // Formatting
    println!("{:>10}", "right");   // "     right"
    println!("{:<10}", "left");    // "left      "
    println!("{:^10}", "center");  // "  center  "
    println!("{:0>5}", 42);        // "00042"
    println!("{:.2}", 3.14159);    // "3.14"
}

eprintln! และ eprint!

พิมพ์ไปยัง stderr:

fn main() {
    eprintln!("Error: something went wrong!");
    eprint!("Warning: ");
    eprintln!("check your input");
}

format!

สร้าง String โดยไม่พิมพ์:

fn main() {
    let s = format!("Hello, {}!", "World");
    println!("{}", s);

    let num = 42;
    let formatted = format!("Number: {:05}", num);  // "Number: 00042"

    // Useful for building strings
    let mut log = String::new();
    log.push_str(&format!("[INFO] Started at {}\n", "10:00"));
    log.push_str(&format!("[INFO] Finished at {}\n", "10:05"));
    println!("{}", log);
}

vec!

สร้าง Vec:

fn main() {
    // With elements
    let v1 = vec![1, 2, 3, 4, 5];

    // Repeat value
    let v2 = vec![0; 5];  // [0, 0, 0, 0, 0]

    // Empty with type
    let v3: Vec<String> = vec![];

    // With expressions
    let v4 = vec![1 + 1, 2 + 2, 3 + 3];  // [2, 4, 6]

    println!("{:?}", v1);
}

Assertion Macros

assert!

fn main() {
    assert!(true);
    assert!(1 + 1 == 2);

    // With message
    let x = 5;
    assert!(x > 0, "x must be positive!");
    assert!(x > 0, "x was {}, expected positive", x);
}

assert_eq! และ assert_ne!

fn main() {
    let a = 4;
    let b = 2 + 2;

    assert_eq!(a, b);     // passes
    assert_ne!(a, 5);     // passes

    // With message
    assert_eq!(a, b, "Expected {} to equal {}", a, b);
}

debug_assert!

เฉพาะ debug builds:

fn process(x: i32) {
    // ถูกลบใน release builds
    debug_assert!(x > 0, "x must be positive in debug mode");
    println!("Processing {}", x);
}

fn main() {
    process(5);
}

dbg!

Debug print พร้อม file/line:

fn main() {
    let x = 5;
    dbg!(x);  // [src/main.rs:3] x = 5

    // Returns the value
    let y = dbg!(x * 2) + 1;  // [src/main.rs:6] x * 2 = 10
    dbg!(y);  // [src/main.rs:7] y = 11

    // Multiple values
    let a = 1;
    let b = 2;
    dbg!(a, b, a + b);
}

todo! และ unimplemented!

Placeholder สำหรับโค้ดที่ยังไม่เสร็จ:

fn not_done_yet() -> i32 {
    todo!("implement this function")  // panics with message
}

fn old_way() -> i32 {
    unimplemented!("use new_way() instead")
}

fn main() {
    // not_done_yet();  // would panic
}

unreachable!

สำหรับโค้ดที่ไม่ควรถึง:

fn divide(a: i32, b: i32) -> i32 {
    match b {
        0 => panic!("division by zero"),
        _ => a / b,
    }
}

fn process(x: i32) {
    match x {
        1..=100 => println!("Valid"),
        _ if x > 100 => println!("Too big"),
        _ if x < 1 => println!("Too small"),
        _ => unreachable!("all cases covered"),
    }
}

panic!

หยุดโปรแกรมทันที:

fn main() {
    let v = vec![1, 2, 3];

    if v.is_empty() {
        panic!("Vector is empty!");
    }

    // With format
    let index = 10;
    if index >= v.len() {
        panic!("Index {} out of bounds for length {}", index, v.len());
    }
}

include_str! และ include_bytes!

รวมไฟล์ใน binary:

// Include as string
const README: &str = include_str!("../../README.md");

// Include as bytes
const LOGO: &[u8] = include_bytes!("../../logo.png");

fn main() {
    println!("First 100 chars of README:");
    println!("{}", &README[..100.min(README.len())]);

    println!("Logo size: {} bytes", LOGO.len());
}

concat! และ stringify!

Compile-time string operations:

const VERSION: &str = concat!("v", "1", ".", "0");

fn main() {
    println!("Version: {}", VERSION);  // "v1.0"

    // stringify! converts expression to string literal
    let x = 1 + 2;
    println!("{} = {}", stringify!(1 + 2), x);  // "1 + 2 = 3"
}

cfg!

Check compile-time configuration:

fn main() {
    if cfg!(target_os = "windows") {
        println!("Running on Windows");
    } else if cfg!(target_os = "linux") {
        println!("Running on Linux");
    } else if cfg!(target_os = "macos") {
        println!("Running on macOS");
    }

    if cfg!(debug_assertions) {
        println!("Debug mode");
    } else {
        println!("Release mode");
    }
}

ลองทำดู! 🎯

  1. ใช้ format! สร้าง formatted string
  2. ใช้ dbg! debug expression
  3. ใช้ cfg! ตรวจสอบ OS

สรุปบทที่ 18

MacroPurposeReturns
println!Print to stdout()
eprintln!Print to stderr()
format!Create StringString
vec!Create VecVec<T>
assert!Check condition() or panic
assert_eq!Check equality() or panic
dbg!Debug printSame as input
todo!Placeholderpanic
panic!Stop programnever
include_str!Include file&str
cfg!Check configbool

👉 ต่อไป: บทที่ 19: Web Development

บทที่ 19: Web Development

สร้าง Web Applications ด้วย Rust

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Web ด้วย RustEcosystem overview
Axum พื้นฐานFramework ยอดนิยม
REST APICRUD operations
Databaseเชื่อมต่อ SQLx

👉 Web ด้วย Rust

Web ด้วย Rust

Frameworks ยอดนิยม

Frameworkคำอธิบาย
AxumModern, tower-based
Actix-webHigh performance
RocketEasy to use
WarpComposable filters

🏗️ Web Application Architecture

+-------------------------------------------------------------------+
|                    Rust Web Application Stack                     |
+-------------------------------------------------------------------+
|                                                                   |
|   Client (Browser/Mobile)                                         |
|         |                                                         |
|         v                                                         |
|   +---------------------------------------------------------------+
|   |                      Load Balancer                            |
|   |                    (nginx/traefik)                            |
|   +---------------------------------------------------------------+
|         |                                                         |
|         v                                                         |
|   +---------------------------------------------------------------+
|   |              Rust Web Server (Axum/Actix)                     |
|   |  +----------+  +----------+  +------------+                   |
|   |  | Routes   |  | Handlers |  | Middleware |                   |
|   |  +----------+  +----------+  +------------+                   |
|   +---------------------------------------------------------------+
|         |                                                         |
|         v                                                         |
|   +---------------------------------------------------------------+
|   |            Database (PostgreSQL/SQLite/Redis)                 |
|   +---------------------------------------------------------------+
|                                                                   |
+-------------------------------------------------------------------+

ทำไมใช้ Rust สำหรับ Web?

คุณสมบัติRustNode.jsPython
Performance⭐⭐⭐⭐⭐
MemoryLowMediumHigh
Concurrency⭐⭐⭐⭐⭐
Type Safety⭐⭐⭐

🚀 Deployment Options

Optionตัวอย่างเหมาะกับ
Dockerdocker build -t myapp .Production
CloudAWS ECS, Google Cloud RunScalable
ServerlessAWS Lambda (+ cargo-lambda)Low traffic
VPSDigitalOcean, LinodeSimple deploy

เราจะใช้ Axum

Axum เป็น framework จาก Tokio team:

  • ใช้ง่าย
  • Async native
  • Type-safe
  • Extensible

👉 ต่อไป: Axum พื้นฐาน

Axum พื้นฐาน

Axum เป็น web framework จาก Tokio team ออกแบบมาสำหรับ async Rust

Setup

# Cargo.toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Hello World

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    // สร้าง router
    let app = Router::new()
        .route("/", get(hello));

    // สร้าง TCP listener
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    println!("🚀 Server running at http://localhost:3000");

    // รัน server
    axum::serve(listener, app).await.unwrap();
}

async fn hello() -> &'static str {
    "Hello, World!"
}

รัน:

cargo run
```text

เปิดเบราว์เซอร์ไปที่ http://localhost:3000

---

## Routes

### Basic Routes

```rust,ignore
let app = Router::new()
    .route("/", get(index))
    .route("/about", get(about))
    .route("/contact", get(contact));

HTTP Methods

use axum::routing::{get, post, put, delete};

let app = Router::new()
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user));

Nested Routes

let api_routes = Router::new()
    .route("/users", get(list_users))
    .route("/posts", get(list_posts));

let app = Router::new()
    .nest("/api/v1", api_routes)
    .route("/", get(index));

// ผลลัพธ์:
// GET /api/v1/users
// GET /api/v1/posts
// GET /

Handler Functions

Basic Handler

async fn hello() -> &'static str {
    "Hello, World!"
}

async fn json_response() -> axum::Json<serde_json::Value> {
    axum::Json(serde_json::json!({
        "message": "Hello JSON"
    }))
}

Return Types

TypeDescription
&'static strPlain text
StringDynamic text
Json\<T\>JSON response
Html\<String\>HTML response
(StatusCode, T)Custom status
Result\<T, E\>With error handling
use axum::{response::Html, http::StatusCode};

async fn html_page() -> Html<String> {
    Html("<h1>Hello HTML</h1>".to_string())
}

async fn custom_status() -> (StatusCode, &'static str) {
    (StatusCode::CREATED, "Created!")
}

Extractors

ดึงข้อมูลจาก request:

Path Parameters

use axum::extract::Path;

// GET /users/123
async fn get_user(Path(id): Path<u32>) -> String {
    format!("User ID: {}", id)
}

// GET /users/123/posts/456
async fn get_user_post(Path((user_id, post_id)): Path<(u32, u32)>) -> String {
    format!("User {} Post {}", user_id, post_id)
}

Query Parameters

use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct SearchParams {
    q: String,
    page: Option<u32>,
}

// GET /search?q=rust&page=2
async fn search(Query(params): Query<SearchParams>) -> String {
    format!("Searching: '{}' page {:?}", params.q, params.page)
}

JSON Body

use axum::extract::Json;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

// POST /users with JSON body
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
    let user = User {
        id: 1,
        name: payload.name,
        email: payload.email,
    };
    Json(user)
}

Headers

use axum::http::HeaderMap;

async fn read_headers(headers: HeaderMap) -> String {
    if let Some(auth) = headers.get("authorization") {
        format!("Auth: {:?}", auth)
    } else {
        "No auth header".to_string()
    }
}

State

แชร์ข้อมูลระหว่าง handlers:

use axum::extract::State;
use std::sync::Arc;

struct AppState {
    db_pool: String,  // ปกติใช้ connection pool จริง
}

#[tokio::main]
async fn main() {
    let state = Arc::new(AppState {
        db_pool: "postgres://...".to_string(),
    });

    let app = Router::new()
        .route("/", get(handler))
        .with_state(state);

    // ...
}

async fn handler(State(state): State<Arc<AppState>>) -> String {
    format!("DB: {}", state.db_pool)
}

Error Handling

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
};

enum AppError {
    NotFound,
    InternalError(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, "Not found"),
            AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.leak()),
        };
        (status, message).into_response()
    }
}

async fn fallible_handler() -> Result<String, AppError> {
    // อาจ return Err(AppError::NotFound)
    Ok("Success!".to_string())
}

Middleware

use axum::middleware;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(CorsLayer::permissive())
    .layer(TraceLayer::new_for_http());

ลองทำดู! 🎯

  1. สร้าง API ที่มี GET และ POST
  2. ใช้ Path extractor รับ id
  3. ใช้ Json extractor รับ body

🌍 Complete CRUD API Example

ตัวอย่าง Todo API แบบครบ:

use axum::{
    extract::{Path, State},
    http::StatusCode,
    routing::{get, post, put, delete},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock};

// Data models
#[derive(Clone, Serialize, Deserialize)]
struct Todo {
    id: u32,
    title: String,
    completed: bool,
}

#[derive(Deserialize)]
struct CreateTodo {
    title: String,
}

type Db = Arc<RwLock<Vec<Todo>>>;

// Handlers
async fn list_todos(State(db): State<Db>) -> Json<Vec<Todo>> {
    let todos = db.read().unwrap().clone();
    Json(todos)
}

async fn create_todo(
    State(db): State<Db>,
    Json(input): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
    let mut todos = db.write().unwrap();
    let id = todos.len() as u32 + 1;
    let todo = Todo {
        id,
        title: input.title,
        completed: false,
    };
    todos.push(todo.clone());
    (StatusCode::CREATED, Json(todo))
}

async fn get_todo(
    State(db): State<Db>,
    Path(id): Path<u32>,
) -> Result<Json<Todo>, StatusCode> {
    let todos = db.read().unwrap();
    todos
        .iter()
        .find(|t| t.id == id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

async fn delete_todo(
    State(db): State<Db>,
    Path(id): Path<u32>,
) -> StatusCode {
    let mut todos = db.write().unwrap();
    if let Some(pos) = todos.iter().position(|t| t.id == id) {
        todos.remove(pos);
        StatusCode::NO_CONTENT
    } else {
        StatusCode::NOT_FOUND
    }
}

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(RwLock::new(vec![]));

    let app = Router::new()
        .route("/todos", get(list_todos).post(create_todo))
        .route("/todos/:id", get(get_todo).delete(delete_todo))
        .with_state(db);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    println!("🚀 Server running at http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

📋 API Endpoints

MethodEndpointDescription
GET/todosList all todos
POST/todosCreate new todo
GET/todos/:idGet todo by ID
DELETE/todos/:idDelete todo

🚀 Deployment Checklist

+-------------------------------------------------------------------+
|                    Production Deployment                          |
+-------------------------------------------------------------------+
|                                                                   |
|   1. Build Release                                                |
|      cargo build --release                                        |
|                                                                   |
|   2. Dockerfile                                                   |
|      FROM rust:1.75 as builder                                    |
|      WORKDIR /app                                                 |
|      COPY . .                                                     |
|      RUN cargo build --release                                    |
|                                                                   |
|      FROM debian:bookworm-slim                                    |
|      COPY --from=builder /app/target/release/myapp /usr/local/bin |
|      CMD ["myapp"]                                                |
|                                                                   |
|   3. Environment Variables                                        |
|      DATABASE_URL=postgres://...                                  |
|      PORT=3000                                                    |
|                                                                   |
|   4. Deploy Options                                               |
|      * Docker -> AWS ECS / Google Cloud Run                       |
|      * Binary -> VPS (DigitalOcean, Linode)                       |
|      * Serverless -> AWS Lambda + cargo-lambda                    |
|                                                                   |
+-------------------------------------------------------------------+

สรุป

ConceptExample
Route.route("/path", get(handler))
PathPath(id): Path<u32>
QueryQuery(params): Query<T>
JSONJson(body): Json<T>
StateState(state): State<Arc<T>>
Nested.nest("/api", routes)

👉 ต่อไป: สร้าง REST API

สร้าง REST API

สร้าง CRUD API ด้วย Axum

Project Setup

# Cargo.toml
[package]
name = "todo-api"
version = "0.1.0"
edition = "2024"

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1", features = ["v4", "serde"] }

Data Model

use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Todo {
    pub id: Uuid,
    pub title: String,
    pub description: Option<String>,
    pub completed: bool,
    pub created_at: String,
}

#[derive(Debug, Deserialize)]
pub struct CreateTodo {
    pub title: String,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateTodo {
    pub title: Option<String>,
    pub description: Option<String>,
    pub completed: Option<bool>,
}

Application State

use std::sync::Arc;
use tokio::sync::RwLock;
use std::collections::HashMap;

pub type Db = Arc<RwLock<HashMap<Uuid, Todo>>>;

fn create_db() -> Db {
    Arc::new(RwLock::new(HashMap::new()))
}

ใช้ RwLock แทน Mutex เพราะ:

  • อ่านได้หลาย concurrent readers
  • เขียนได้ทีละคน

Complete API

use axum::{
    extract::{Path, State, Json},
    http::StatusCode,
    routing::{get, post, put, delete},
    Router,
    response::IntoResponse,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use std::collections::HashMap;
use uuid::Uuid;

// === Models ===

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo {
    id: Uuid,
    title: String,
    description: Option<String>,
    completed: bool,
}

#[derive(Debug, Deserialize)]
struct CreateTodo {
    title: String,
    description: Option<String>,
}

#[derive(Debug, Deserialize)]
struct UpdateTodo {
    title: Option<String>,
    description: Option<String>,
    completed: Option<bool>,
}

type Db = Arc<RwLock<HashMap<Uuid, Todo>>>;

// === Handlers ===

// GET /todos
async fn list_todos(State(db): State<Db>) -> Json<Vec<Todo>> {
    let todos = db.read().await;
    let list: Vec<Todo> = todos.values().cloned().collect();
    Json(list)
}

// GET /todos/:id
async fn get_todo(
    Path(id): Path<Uuid>,
    State(db): State<Db>,
) -> Result<Json<Todo>, StatusCode> {
    let todos = db.read().await;

    todos
        .get(&id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

// POST /todos
async fn create_todo(
    State(db): State<Db>,
    Json(input): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
    let todo = Todo {
        id: Uuid::new_v4(),
        title: input.title,
        description: input.description,
        completed: false,
    };

    db.write().await.insert(todo.id, todo.clone());

    (StatusCode::CREATED, Json(todo))
}

// PUT /todos/:id
async fn update_todo(
    Path(id): Path<Uuid>,
    State(db): State<Db>,
    Json(input): Json<UpdateTodo>,
) -> Result<Json<Todo>, StatusCode> {
    let mut todos = db.write().await;

    let todo = todos.get_mut(&id).ok_or(StatusCode::NOT_FOUND)?;

    if let Some(title) = input.title {
        todo.title = title;
    }
    if let Some(description) = input.description {
        todo.description = Some(description);
    }
    if let Some(completed) = input.completed {
        todo.completed = completed;
    }

    Ok(Json(todo.clone()))
}

// DELETE /todos/:id
async fn delete_todo(
    Path(id): Path<Uuid>,
    State(db): State<Db>,
) -> StatusCode {
    let mut todos = db.write().await;

    if todos.remove(&id).is_some() {
        StatusCode::NO_CONTENT
    } else {
        StatusCode::NOT_FOUND
    }
}

// === Main ===

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(RwLock::new(HashMap::new()));

    let app = Router::new()
        .route("/todos", get(list_todos).post(create_todo))
        .route("/todos/:id", get(get_todo).put(update_todo).delete(delete_todo))
        .with_state(db);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    println!("🚀 Server running at http://localhost:3000");

    axum::serve(listener, app).await.unwrap();
}

Testing with curl

# Create todo
curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn Rust", "description": "Complete the tutorial"}'

# List all todos
curl http://localhost:3000/todos

# Get single todo
curl http://localhost:3000/todos/{id}

# Update todo
curl -X PUT http://localhost:3000/todos/{id} \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

# Delete todo
curl -X DELETE http://localhost:3000/todos/{id}

Error Handling

use axum::response::{IntoResponse, Response};

#[derive(Debug)]
enum AppError {
    NotFound,
    InvalidInput(String),
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, "Not found"),
            AppError::InvalidInput(msg) => (StatusCode::BAD_REQUEST, msg.leak()),
            AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.leak()),
        };

        (status, Json(serde_json::json!({
            "error": message
        }))).into_response()
    }
}

// Use in handler
async fn get_todo_v2(
    Path(id): Path<Uuid>,
    State(db): State<Db>,
) -> Result<Json<Todo>, AppError> {
    let todos = db.read().await;

    todos
        .get(&id)
        .cloned()
        .map(Json)
        .ok_or(AppError::NotFound)
}

Add Middleware (CORS, Logging)

use tower_http::cors::{CorsLayer, Any};
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/todos", get(list_todos).post(create_todo))
    .route("/todos/:id", get(get_todo).put(update_todo).delete(delete_todo))
    .with_state(db)
    .layer(CorsLayer::new().allow_origin(Any).allow_methods(Any))
    .layer(TraceLayer::new_for_http());

ลองทำดู! 🎯

  1. เพิ่ม field priority ใน Todo
  2. เพิ่ม endpoint GET /todos?completed=true filter
  3. เพิ่ม validation สำหรับ title (ไม่ว่าง)

สรุป

EndpointMethodDescription
/todosGETList all
/todosPOSTCreate
/todos/:idGETGet one
/todos/:idPUTUpdate
/todos/:idDELETEDelete

👉 ต่อไป: เชื่อมต่อ Database

เชื่อมต่อ Database

ใช้ SQLx เชื่อมต่อ PostgreSQL, MySQL, หรือ SQLite

Setup

# Cargo.toml
[dependencies]
# PostgreSQL
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }

# หรือ SQLite (ง่ายกว่าสำหรับพัฒนา)
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }

# หรือ MySQL
sqlx = { version = "0.7", features = ["runtime-tokio", "mysql"] }

tokio = { version = "1", features = ["full"] }

SQLite Example (Beginner-friendly)

use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    // Create database file if not exists
    let pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect("sqlite:todos.db?mode=create")
        .await?;

    // Create table
    sqlx::query(
        r#"
        CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            completed BOOLEAN NOT NULL DEFAULT 0
        )
        "#
    )
    .execute(&pool)
    .await?;

    println!("Database ready!");

    Ok(())
}

PostgreSQL Connection Pool

use sqlx::postgres::{PgPool, PgPoolOptions};

async fn create_pool() -> Result<PgPool, sqlx::Error> {
    let database_url = std::env::var("DATABASE_URL")
        .unwrap_or_else(|_| "postgres://user:pass@localhost/mydb".to_string());

    PgPoolOptions::new()
        .max_connections(10)
        .min_connections(2)
        .acquire_timeout(std::time::Duration::from_secs(30))
        .connect(&database_url)
        .await
}

#[tokio::main]
async fn main() {
    let pool = create_pool().await.expect("Failed to create pool");
    println!("Connected to database!");
}

Basic Queries

Execute (INSERT, UPDATE, DELETE)

use sqlx::PgPool;

async fn insert_todo(pool: &PgPool, title: &str) -> Result<i64, sqlx::Error> {
    let result = sqlx::query(
        "INSERT INTO todos (title, completed) VALUES ($1, false)"
    )
    .bind(title)
    .execute(pool)
    .await?;

    Ok(result.rows_affected() as i64)
}

async fn update_todo(pool: &PgPool, id: i32, completed: bool) -> Result<bool, sqlx::Error> {
    let result = sqlx::query(
        "UPDATE todos SET completed = $1 WHERE id = $2"
    )
    .bind(completed)
    .bind(id)
    .execute(pool)
    .await?;

    Ok(result.rows_affected() > 0)
}

async fn delete_todo(pool: &PgPool, id: i32) -> Result<bool, sqlx::Error> {
    let result = sqlx::query("DELETE FROM todos WHERE id = $1")
        .bind(id)
        .execute(pool)
        .await?;

    Ok(result.rows_affected() > 0)
}

Fetch with FromRow

use sqlx::FromRow;

#[derive(Debug, FromRow)]
struct Todo {
    id: i32,
    title: String,
    completed: bool,
}

async fn get_all_todos(pool: &PgPool) -> Result<Vec<Todo>, sqlx::Error> {
    sqlx::query_as::<_, Todo>("SELECT id, title, completed FROM todos")
        .fetch_all(pool)
        .await
}

async fn get_todo_by_id(pool: &PgPool, id: i32) -> Result<Option<Todo>, sqlx::Error> {
    sqlx::query_as::<_, Todo>("SELECT id, title, completed FROM todos WHERE id = $1")
        .bind(id)
        .fetch_optional(pool)
        .await
}

async fn get_completed_todos(pool: &PgPool) -> Result<Vec<Todo>, sqlx::Error> {
    sqlx::query_as::<_, Todo>("SELECT * FROM todos WHERE completed = true")
        .fetch_all(pool)
        .await
}

query_as! Macro (Compile-time checked)

// ต้อง set DATABASE_URL env var
// และรัน: cargo sqlx prepare

async fn get_users_checked(pool: &PgPool) -> Result<Vec<User>, sqlx::Error> {
    sqlx::query_as!(
        User,
        r#"SELECT id, name, email FROM users WHERE active = true"#
    )
    .fetch_all(pool)
    .await
}

async fn create_user_checked(
    pool: &PgPool,
    name: &str,
    email: &str
) -> Result<User, sqlx::Error> {
    sqlx::query_as!(
        User,
        r#"
        INSERT INTO users (name, email)
        VALUES ($1, $2)
        RETURNING id, name, email
        "#,
        name,
        email
    )
    .fetch_one(pool)
    .await
}

Transactions

use sqlx::PgPool;

async fn transfer_money(
    pool: &PgPool,
    from_id: i32,
    to_id: i32,
    amount: f64,
) -> Result<(), sqlx::Error> {
    let mut tx = pool.begin().await?;

    // Deduct from sender
    sqlx::query("UPDATE accounts SET balance = balance - $1 WHERE id = $2")
        .bind(amount)
        .bind(from_id)
        .execute(&mut *tx)
        .await?;

    // Add to receiver
    sqlx::query("UPDATE accounts SET balance = balance + $1 WHERE id = $2")
        .bind(amount)
        .bind(to_id)
        .execute(&mut *tx)
        .await?;

    // Commit transaction
    tx.commit().await?;

    Ok(())
}

Integration with Axum

use axum::{
    extract::{State, Path, Json},
    routing::get,
    Router,
    http::StatusCode,
};
use sqlx::PgPool;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

async fn list_todos(State(state): State<AppState>) -> Json<Vec<Todo>> {
    let todos = sqlx::query_as::<_, Todo>("SELECT * FROM todos")
        .fetch_all(&state.db)
        .await
        .unwrap_or_default();

    Json(todos)
}

async fn create_todo(
    State(state): State<AppState>,
    Json(input): Json<CreateTodo>,
) -> Result<(StatusCode, Json<Todo>), StatusCode> {
    let todo = sqlx::query_as::<_, Todo>(
        "INSERT INTO todos (title) VALUES ($1) RETURNING *"
    )
    .bind(&input.title)
    .fetch_one(&state.db)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok((StatusCode::CREATED, Json(todo)))
}

#[tokio::main]
async fn main() {
    let pool = PgPoolOptions::new()
        .connect("postgres://user:pass@localhost/mydb")
        .await
        .expect("Failed to connect");

    let state = AppState { db: pool };

    let app = Router::new()
        .route("/todos", get(list_todos).post(create_todo))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

Migrations

# Install sqlx-cli
cargo install sqlx-cli

# Create migration
sqlx migrate add create_todos_table

# Edit migration file: migrations/20231225_create_todos_table.sql
# CREATE TABLE todos (...)

# Run migrations
sqlx migrate run

ลองทำดู! 🎯

  1. สร้าง SQLite database และ table
  2. เขียน CRUD functions
  3. เชื่อมต่อกับ Axum handlers

สรุปบทที่ 19

ConceptDescription
PgPoolConnection pool
query()Basic query
query_as()Query with struct mapping
query_as!()Compile-time checked
FromRowDerive for struct
begin()Start transaction
commit()Commit transaction

Error Handling

match result {
    Ok(todo) => println!("Got: {:?}", todo),
    Err(sqlx::Error::RowNotFound) => println!("Not found"),
    Err(e) => eprintln!("Database error: {}", e),
}

👉 ต่อไป: บทที่ 20: Final Project

บทที่ 20: Final Project - โปรเจกต์จบ 🎓

นำความรู้ทั้งหมดมาสร้าง CLI Todo App ที่สมบูรณ์!

สิ่งที่จะได้เรียนรู้

หัวข้อคำอธิบาย
Project Overviewสิ่งที่จะสร้าง
การออกแบบโครงสร้างและ modules
ImplementationStep-by-step coding
สรุปReview สิ่งที่เรียนมา

Features ที่จะสร้าง

  • ✅ Add tasks
  • ✅ List tasks
  • ✅ Complete tasks
  • ✅ Remove tasks
  • ✅ Save to file

👉 Project Overview

Project Overview

Todo CLI App

สร้าง command-line todo app ที่มี features:

$ todo add "Learn Rust"
✅ Added: Learn Rust

$ todo list
1. [ ] Learn Rust
2. [x] Read the book

$ todo complete 1
✅ Completed: Learn Rust

$ todo remove 1
✅ Removed: Learn Rust

🏗️ Project Architecture

+-------------------------------------------------------------------+
|                    Todo CLI App Architecture                      |
+-------------------------------------------------------------------+
|                                                                   |
|   +---------------------------------------------------------------+
|   |                     main.rs (Entry Point)                     |
|   |  * Parse command-line arguments                               |
|   |  * Route to appropriate handler                               |
|   +-------------------------------+-------------------------------+
|                                   |                               |
|           +-----------------------+-----------------------+       |
|           |                       |                       |       |
|           v                       v                       v       |
|   +------------+           +------------+           +------------+|
|   |   todo.rs  |           | storage.rs |           |   cli.rs   ||
|   |  (Model)   |           |   (I/O)    |           | (Interface)||
|   |            |           |            |           |            ||
|   | struct Todo|           | load/save  |           | add, list  ||
|   | enum Status|           | JSON file  |           | complete   ||
|   +------------+           +------------+           +------------+|
|                                   |                               |
|                                   v                               |
|                             todos.json                            |
|                                                                   |
+-------------------------------------------------------------------+

ความรู้ที่ใช้

บทความรู้
2Variables & Types
5Ownership
6Structs
7Enums
8Collections (Vec)
9Error Handling
11Modules

Dependencies

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

👉 ต่อไป: การออกแบบ

การออกแบบ

ออกแบบ CLI Todo App ให้รองรับการขยาย

Requirements

Functional Requirements

  1. เพิ่ม todo ใหม่ได้
  2. แสดงรายการ todos ทั้งหมด
  3. ทำเครื่องหมาย complete ได้
  4. ลบ todo ได้
  5. บันทึกลงไฟล์ (persistent)

Non-functional Requirements

  1. ใช้งานง่าย (simple CLI)
  2. Error handling ที่ดี
  3. Code ที่ test ได้
  4. แยก concerns ชัดเจน

โครงสร้างโปรเจกต์

todo-cli/
├── Cargo.toml
├── src/
│   ├── main.rs      ← Entry point, CLI parsing
│   ├── lib.rs       ← Application logic
│   ├── todo.rs      ← Todo struct
│   └── storage.rs   ← File I/O
├── tests/
│   └── integration_test.rs
└── todos.json       ← Data storage

Architecture Diagram

+---------------------------------------------+
|                   main.rs                   |
|   - Parse CLI arguments                     |
|   - Create Command enum                     |
|   - Call lib::run()                         |
+----------------------+----------------------+
                       |
                       v
+---------------------------------------------+
|                   lib.rs                    |
|   - Application logic                       |
|   - Handle Commands                         |
|   - Orchestrate todo + storage              |
+----------------------+----------------------+
                       |
         +-------------+-------------+
         v                           v
+-----------------+         +-----------------+
|    todo.rs      |         |   storage.rs    |
|   - Todo struct |         |   - load()      |
|   - new()       |         |   - save()      |
|   - toggle()    |         |   - JSON I/O    |
+-----------------+         +-----------------+

Data Structures

Todo Struct

// src/todo.rs
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Todo {
    pub id: u32,
    pub title: String,
    pub completed: bool,
    pub created_at: DateTime<Utc>,
    pub completed_at: Option<DateTime<Utc>>,
}

impl Todo {
    pub fn new(id: u32, title: String) -> Self {
        Self {
            id,
            title,
            completed: false,
            created_at: Utc::now(),
            completed_at: None,
        }
    }

    pub fn toggle(&mut self) {
        self.completed = !self.completed;
        if self.completed {
            self.completed_at = Some(Utc::now());
        } else {
            self.completed_at = None;
        }
    }
}

impl std::fmt::Display for Todo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let status = if self.completed { "✓" } else { " " };
        write!(f, "[{}] {}. {}", status, self.id, self.title)
    }
}

Command Enum

// src/lib.rs
#[derive(Debug, PartialEq)]
pub enum Command {
    Add(String),
    List,
    Complete(u32),
    Uncomplete(u32),
    Remove(u32),
    Clear,
    Help,
}

impl Command {
    pub fn from_args(args: &[String]) -> Self {
        match args.get(1).map(|s| s.as_str()) {
            Some("add") => {
                let title = args[2..].join(" ");
                if title.is_empty() {
                    Command::Help
                } else {
                    Command::Add(title)
                }
            }
            Some("list") | Some("ls") => Command::List,
            Some("complete") | Some("done") => {
                args.get(2)
                    .and_then(|s| s.parse().ok())
                    .map(Command::Complete)
                    .unwrap_or(Command::Help)
            }
            Some("uncomplete") | Some("undo") => {
                args.get(2)
                    .and_then(|s| s.parse().ok())
                    .map(Command::Uncomplete)
                    .unwrap_or(Command::Help)
            }
            Some("remove") | Some("rm") => {
                args.get(2)
                    .and_then(|s| s.parse().ok())
                    .map(Command::Remove)
                    .unwrap_or(Command::Help)
            }
            Some("clear") => Command::Clear,
            _ => Command::Help,
        }
    }
}

Modules Responsibility

ModuleResponsibilityDependencies
main.rsCLI parsing, entry pointlib
lib.rsApplication logictodo, storage
todo.rsTodo data modelserde, chrono
storage.rsFile I/Oserde_json, todo

Error Handling Strategy

// src/lib.rs
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Todo with id {0} not found")]
    NotFound(u32),

    #[error("Storage error: {0}")]
    Storage(#[from] StorageError),

    #[error("Invalid command")]
    InvalidCommand,
}

// src/storage.rs
#[derive(Error, Debug)]
pub enum StorageError {
    #[error("Failed to read file: {0}")]
    ReadError(#[from] std::io::Error),

    #[error("Failed to parse JSON: {0}")]
    ParseError(#[from] serde_json::Error),
}

Dependencies

# Cargo.toml
[package]
name = "todo-cli"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
thiserror = "1.0"

CLI Design

# Add todo
$ todo add Buy groceries
✅ Added: Buy groceries

# List todos
$ todo list
[✓] 1. Buy groceries
[ ] 2. Read Rust book
[ ] 3. Exercise

# Complete todo
$ todo complete 2
✅ Completed: Read Rust book

# Remove todo
$ todo remove 1
✅ Removed: Buy groceries

# Help
$ todo help
Usage: todo <command> [args]

Commands:
  add <title>      Add a new todo
  list             List all todos
  complete <id>    Mark todo as complete
  remove <id>      Remove a todo
  clear            Remove all todos
  help             Show this help

ลองทำดู! 🎯

  1. ออกแบบ struct เพิ่มเติม (priority, tags)
  2. วาด sequence diagram สำหรับ “add todo”
  3. ออกแบบ error types

สรุป

ComponentRole
main.rsParse args → Command → run()
lib.rsHandle command → update state
todo.rsData model
storage.rsPersistence layer

Design Principles Used

  • Single Responsibility: แต่ละ module ทำหน้าที่เดียว
  • Dependency Injection: Storage trait สำหรับ testing
  • Error Handling: Custom error types พร้อม context
  • Separation of Concerns: UI/Logic/Data แยกกัน

👉 ต่อไป: Implementation

Implementation

main.rs

use std::env;
use todo_cli::{run, Command};

fn main() {
    let args: Vec<String> = env::args().collect();

    let command = match args.get(1).map(|s| s.as_str()) {
        Some("add") => {
            let title = args[2..].join(" ");
            Command::Add(title)
        }
        Some("list") => Command::List,
        Some("complete") => {
            let id: u32 = args[2].parse().expect("Invalid ID");
            Command::Complete(id)
        }
        Some("remove") => {
            let id: u32 = args[2].parse().expect("Invalid ID");
            Command::Remove(id)
        }
        _ => Command::Help,
    };

    if let Err(e) = run(command) {
        eprintln!("Error: {}", e);
    }
}

lib.rs

mod todo;
mod storage;

pub use todo::Todo;

pub enum Command {
    Add(String),
    List,
    Complete(u32),
    Remove(u32),
    Help,
}

pub fn run(command: Command) -> Result<(), Box<dyn std::error::Error>> {
    let mut todos = storage::load()?;

    match command {
        Command::Add(title) => {
            let id = todos.len() as u32 + 1;
            let todo = Todo::new(id, title.clone());
            todos.push(todo);
            storage::save(&todos)?;
            println!("✅ Added: {}", title);
        }
        Command::List => {
            for todo in &todos {
                let status = if todo.completed { "x" } else { " " };
                println!("{}. [{}] {}", todo.id, status, todo.title);
            }
        }
        Command::Complete(id) => {
            if let Some(todo) = todos.iter_mut().find(|t| t.id == id) {
                todo.completed = true;
                storage::save(&todos)?;
                println!("✅ Completed: {}", todo.title);
            }
        }
        Command::Remove(id) => {
            todos.retain(|t| t.id != id);
            storage::save(&todos)?;
            println!("✅ Removed task {}", id);
        }
        Command::Help => {
            println!("Usage: todo <command> [args]");
            println!("Commands: add, list, complete, remove");
        }
    }

    Ok(())
}

storage.rs

use crate::Todo;
use std::fs;

const FILE_PATH: &str = "todos.json";

pub fn load() -> Result<Vec<Todo>, Box<dyn std::error::Error>> {
    if !std::path::Path::new(FILE_PATH).exists() {
        return Ok(vec![]);
    }

    let content = fs::read_to_string(FILE_PATH)?;
    let todos: Vec<Todo> = serde_json::from_str(&content)?;
    Ok(todos)
}

pub fn save(todos: &[Todo]) -> Result<(), Box<dyn std::error::Error>> {
    let content = serde_json::to_string_pretty(todos)?;
    fs::write(FILE_PATH, content)?;
    Ok(())
}

👉 ต่อไป: สรุป

สรุป 🎉

ยินดีด้วย! คุณจบคอร์ส Rust แล้ว!

คุณได้เรียนรู้ภาษา Rust จากพื้นฐานจนถึงขั้นสูง และสร้าง CLI application ที่ใช้งานได้จริง!


สิ่งที่คุณได้เรียนรู้

Part 1: พื้นฐาน (บทที่ 1-4)

บทหัวข้อKey Concepts
1Getting StartedInstallation, Cargo, Hello World
2VariablesMutability, Data Types, Constants
3FunctionsParameters, Return Values, Expressions
4Control FlowIf/Else, Loops, Match

Part 2: Core Concepts (บทที่ 5-9)

บทหัวข้อKey Concepts
5OwnershipThe 3 Rules, Move, Clone, References
6StructsDefining, Methods, Associated Functions
7EnumsOption, Result, Pattern Matching
8CollectionsVec, String, HashMap
9Error Handlingpanic!, Result, ? Operator

Part 3: Advanced (บทที่ 10-14)

บทหัวข้อKey Concepts
10Generics & TraitsType Parameters, Trait Bounds, Lifetimes
11ModulesPackages, Crates, Visibility
12TestingUnit Tests, Integration Tests, Doc Tests
13Iterators & ClosuresAdapters, Consumers, Fn Traits
14Smart PointersBox, Rc, RefCell, Weak

Part 4: Systems Programming (บทที่ 15-18)

บทหัวข้อKey Concepts
15ConcurrencyThreads, Channels, Mutex, Arc
16Async/AwaitFutures, Tokio, select!
17UnsafeRaw Pointers, FFI, Safe Abstractions
18MacrosDeclarative, Procedural, Built-in

Part 5: Real World (บทที่ 19-20)

บทหัวข้อKey Concepts
19Web DevelopmentAxum, REST API, SQLx
20Final ProjectCLI App Design & Implementation

Key Skills คุณได้

// 1. Memory safety without GC
let s1 = String::from("hello");
let s2 = &s1;  // Borrowing

// 2. Error handling
fn read_file() -> Result<String, Error> {
    let content = std::fs::read_to_string("file.txt")?;
    Ok(content)
}

// 3. Zero-cost abstractions
let sum: i32 = vec![1, 2, 3]
    .iter()
    .map(|x| x * 2)
    .filter(|x| x > &2)
    .sum();

// 4. Fearless concurrency
std::thread::spawn(move || {
    println!("Running in parallel!");
});

โปรเจกต์ที่ควรลองทำต่อ

Beginner Projects

  1. Guessing Game - Text-based number guessing
  2. Temperature Converter - Fahrenheit ↔ Celsius
  3. Todo List - CLI task manager (บทนี้!)

Intermediate Projects

  1. Markdown Parser - Convert MD to HTML
  2. HTTP Client - Simple curl clone
  3. File Searcher - Like grep command

Advanced Projects

  1. REST API Server - Full CRUD with database
  2. Async Crawler - Web scraper with Tokio
  3. Chat Server - Real-time with WebSockets
  4. Compiler - Simple expression language

Resources สำหรับเรียนต่อ

ดูรายละเอียดเพิ่มเติมที่ � Appendix: Resources


☕ สนับสนุนผู้เขียน

ขอบคุณที่อ่านจนจบครับ! ถ้าหนังสือเล่มนี้มีประโยชน์ คุณสามารถเลี้ยงกาแฟผมเพื่อเป็นกำลังใจในการทำคอนเทนต์ดีๆ ต่อไปได้ที่:

👉 Buy Me a Coffee ☕

Final Tips

  1. อ่าน Error Messages - Rust มี error messages ที่ดีมาก
  2. ใช้ Clippy - cargo clippy หา improvements
  3. Format Code - cargo fmt จัด format อัตโนมัติ
  4. Write Tests - cargo test ทดสอบ code
  5. Read Documentation - cargo doc --open

ขอบคุณที่เรียนกับเรา! 🦀

     _~^~^~_
 \) /  o o  \ (/
   '_   ∿   _'
   / '-----' \

 Happy Coding!

Rust makes systems programming accessible to everyone.

ขอให้โชคดีกับการเขียน Rust!

ภาคผนวก

เนื้อหาเพิ่มเติมสำหรับอ้างอิง


สารบัญ

หัวข้อคำอธิบาย
Rust Cheatsheetสรุป syntax ที่ใช้บ่อย
Cargo Commandsคำสั่ง Cargo ทั้งหมด
Resourcesแหล่งเรียนรู้เพิ่มเติม
แบบฝึกหัด100+ ข้อพร้อมเฉลย
Quizทดสอบความเข้าใจ
Example Codeโค้ดตัวอย่างรันได้

👉 กลับหน้าแรก

Rust Cheatsheet

สรุปคำสั่งและ syntax ที่ใช้บ่อยใน Rust


Variables

// Immutable (ค่าคงที่)
let x = 5;

// Mutable (เปลี่ยนค่าได้)
let mut y = 10;
y = 20;

// Type annotation
let z: i32 = 100;

// Constants (ต้องระบุ type)
const MAX_POINTS: u32 = 100_000;

// Shadowing
let x = 5;
let x = x + 1; // x = 6

Data Types

Scalar Types

Typeตัวอย่าง
i8, i16, i32, i64, i128-128, 42
u8, u16, u32, u64, u1280, 255
f32, f643.14, 2.0
booltrue, false
char'a', '🦀'

Compound Types

// Tuple
let tup: (i32, f64, bool) = (500, 6.4, true);
let (x, y, z) = tup;  // destructuring
let first = tup.0;     // indexing

// Array (fixed size)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let first = arr[0];
let arr = [3; 5]; // [3, 3, 3, 3, 3]

Functions

// Basic function
fn greet() {
    println!("Hello!");
}

// With parameters
fn greet_name(name: &str) {
    println!("Hello, {}!", name);
}

// With return value
fn add(a: i32, b: i32) -> i32 {
    a + b  // no semicolon = return
}

// Early return
fn check(x: i32) -> bool {
    if x > 10 {
        return true;
    }
    false
}

Control Flow

If/Else

if x > 5 {
    println!("Big");
} else if x > 0 {
    println!("Small");
} else {
    println!("Zero or negative");
}

// As expression
let result = if x > 5 { "big" } else { "small" };

Loops

// Infinite loop
loop {
    break;  // exit
}

// While loop
while x > 0 {
    x -= 1;
}

// For loop
for i in 0..5 {
    println!("{}", i);  // 0, 1, 2, 3, 4
}

for item in &vec {
    println!("{}", item);
}

Ownership

// Move
let s1 = String::from("hello");
let s2 = s1;  // s1 is invalid now

// Clone (deep copy)
let s1 = String::from("hello");
let s2 = s1.clone();  // s1 still valid

// Reference (borrow)
let s1 = String::from("hello");
let len = calculate_length(&s1);  // s1 still valid

// Mutable reference
fn change(s: &mut String) {
    s.push_str(" world");
}

Structs

// Define
struct User {
    username: String,
    email: String,
    active: bool,
}

// Create
let user = User {
    username: String::from("john"),
    email: String::from("john@example.com"),
    active: true,
};

// Methods
impl User {
    fn new(username: String) -> Self {
        Self {
            username,
            email: String::new(),
            active: true,
        }
    }

    fn is_active(&self) -> bool {
        self.active
    }
}

Enums & Match

// Define
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

// Match
match msg {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move to {}, {}", x, y),
    Message::Write(text) => println!("Write: {}", text),
}

// Option
let x: Option<i32> = Some(5);
let y: Option<i32> = None;

match x {
    Some(value) => println!("{}", value),
    None => println!("No value"),
}

// if let
if let Some(value) = x {
    println!("{}", value);
}

Error Handling

// Result
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

// Handle Result
match divide(10.0, 2.0) {
    Ok(result) => println!("{}", result),
    Err(e) => println!("Error: {}", e),
}

// ? operator
fn read_file() -> Result<String, std::io::Error> {
    let content = std::fs::read_to_string("file.txt")?;
    Ok(content)
}

// unwrap (panic if error)
let content = std::fs::read_to_string("file.txt").unwrap();

// expect (panic with message)
let content = std::fs::read_to_string("file.txt")
    .expect("Failed to read file");

Collections

Vec

let mut v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];

v.push(4);
v.pop();

let first = &v[0];
let first = v.get(0);  // Option<&i32>

for i in &v {
    println!("{}", i);
}

String

let mut s = String::new();
let s = String::from("hello");
let s = "hello".to_string();

s.push_str(" world");
s.push('!');

let s3 = format!("{} {}", s1, s2);

HashMap

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert("key", "value");

let value = map.get("key");  // Option<&V>

for (key, value) in &map {
    println!("{}: {}", key, value);
}

Iterators

let v = vec![1, 2, 3, 4, 5];

// Common methods
v.iter().map(|x| x * 2);
v.iter().filter(|x| **x > 2);
v.iter().sum::<i32>();
v.iter().collect::<Vec<_>>();
v.iter().for_each(|x| println!("{}", x));
v.iter().find(|x| **x == 3);
v.iter().any(|x| *x > 2);
v.iter().all(|x| *x > 0);

Smart Pointers

// Box - Heap allocation
let b = Box::new(5);

// Rc - Reference counting
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a);

// RefCell - Interior mutability
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() += 1;

Traits

// Define
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation
    fn default_summary(&self) -> String {
        String::from("...")
    }
}

// Implement
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

// Trait bounds
fn notify<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// where clause
fn notify<T>(item: &T)
where
    T: Summary + Clone,
{
    println!("{}", item.summarize());
}

Async

// Async function
async fn fetch_data() -> String {
    // ...
    String::from("data")
}

// Await
async fn process() {
    let data = fetch_data().await;
    println!("{}", data);
}

// Tokio main
#[tokio::main]
async fn main() {
    process().await;
}

👉 Cargo Commands

Cargo Commands

คำสั่ง Cargo ที่ใช้บ่อย


🚀 พื้นฐาน

คำสั่งคำอธิบาย
cargo new project_nameสร้างโปรเจกต์ใหม่ (binary)
cargo new --lib lib_nameสร้างโปรเจกต์ใหม่ (library)
cargo initสร้างโปรเจกต์ในโฟลเดอร์ปัจจุบัน
cargo buildBuild โปรเจกต์ (debug)
cargo build --releaseBuild โปรเจกต์ (release/optimized)
cargo runBuild และ Run
cargo run --releaseRun แบบ release

🧪 Testing

คำสั่งคำอธิบาย
cargo testรัน tests ทั้งหมด
cargo test test_nameรัน test ที่ชื่อตรง
cargo test -- --show-outputแสดง println!
cargo test -- --test-threads=1รัน sequential
cargo test -- --ignoredรัน ignored tests
cargo test --docรัน doc tests

📦 Dependencies

คำสั่งคำอธิบาย
cargo add crate_nameเพิ่ม dependency
cargo add tokio --features fullเพิ่มพร้อม features
cargo remove crate_nameลบ dependency
cargo updateอัปเดต dependencies
cargo treeแสดง dependency tree

📋 ตรวจสอบ

คำสั่งคำอธิบาย
cargo checkCheck ว่า compile ได้ (เร็วกว่า build)
cargo clippyLint หา improvements
cargo fmtFormat code
cargo fmt --checkCheck format
cargo auditตรวจ security vulnerabilities

📖 Documentation

คำสั่งคำอธิบาย
cargo docสร้าง documentation
cargo doc --openสร้างและเปิดใน browser
cargo doc --no-depsไม่รวม dependencies

🔧 Advanced

คำสั่งคำอธิบาย
cargo cleanลบ target directory
cargo publishPublish ไป crates.io
cargo install crate_nameติดตั้ง binary
cargo uninstall crate_nameลบ binary
cargo benchRun benchmarks

🎯 Examples

# สร้างโปรเจกต์ใหม่
cargo new my_project
cd my_project

# เพิ่ม dependencies
cargo add serde --features derive
cargo add tokio --features full

# Build และ Run
cargo run

# ตรวจสอบ code
cargo check
cargo clippy
cargo fmt

# รัน tests
cargo test

# Build สำหรับ production
cargo build --release

# สร้าง documentation
cargo doc --open

📁 Examples

# รัน example
cargo run --example example_name

# รัน specific binary
cargo run --bin binary_name

# รัน workspace member
cargo run -p package_name

🛠️ Workspace

# Cargo.toml (root)
[workspace]
members = [
    "crate1",
    "crate2",
]

# Build all
cargo build --workspace

# Test all
cargo test --workspace

# Run specific
cargo run -p crate1

🔍 Useful Flags

Flagคำอธิบาย
--verbose หรือ -vแสดงรายละเอียด
--quiet หรือ -qลดการแสดงผล
--jobs N หรือ -j Nจำนวน parallel jobs
--target tripleCross compile
--features "f1 f2"เปิด features
--all-featuresเปิดทุก features
--no-default-featuresปิด default features

👉 Resources

Resources

แหล่งเรียนรู้ Rust เพิ่มเติม


📚 Official Documentation

Resourceคำอธิบาย
The Rust Bookหนังสือ Rust อย่างเป็นทางการ
Rust by Exampleเรียนจากตัวอย่าง
Rustlingsแบบฝึกหัดขนาดเล็ก
Rust ReferenceReference ภาษา
Standard LibraryDocumentation std

🎓 Learning Platforms

Platformคำอธิบาย
Exercism Rust Trackแบบฝึกหัด + Mentoring
Rust Quizทดสอบความรู้
Tour of RustInteractive Tutorial
Comprehensive Rustโดย Google

📦 Crate Discovery

Siteคำอธิบาย
crates.ioOfficial Registry
lib.rsAlternative Search
Blessed.rsRecommended Crates
Docs.rsAuto-generated Docs

Web Development

Crateคำอธิบาย
axumWeb framework
actix-webWeb framework
reqwestHTTP client
towerMiddleware

Async

Crateคำอธิบาย
tokioAsync runtime
async-stdAsync runtime
futuresFuture utilities

Serialization

Crateคำอธิบาย
serdeSerialization
serde_jsonJSON
tomlTOML

Database

Crateคำอธิบาย
sqlxAsync SQL
dieselORM
sea-ormAsync ORM

Error Handling

Crateคำอธิบาย
thiserrorCustom errors
anyhowEasy errors

CLI

Crateคำอธิบาย
clapArgument parsing
dialoguerUser prompts
indicatifProgress bars

💬 Community

PlatformLink
DiscordRust Discord
ForumRust Users Forum
Redditr/rust
Twitter/X@rustlang

📺 YouTube Channels

Channelคำอธิบาย
Jon GjengsetDeep dives
Let’s Get RustyTutorials
Ryan LevickMicrosoft Rust
RustOfficial
หัวข้อVideo แนะนำ
OwnershipVisualizing Rust Ownership - Let’s Get Rusty
LifetimesRust Lifetimes Finally Explained - Let’s Get Rusty
AsyncAsync Rust Explained - Jon Gjengset
Smart PointersBox, Rc, Arc Explained - Let’s Get Rusty
Error HandlingError Handling in Rust - Let’s Get Rusty

🎮 Interactive Practice

Resourceคำอธิบาย
Rust Playgroundทดลองโค้ดออนไลน์
Rust Explorerดู Assembly output
GodboltCompiler Explorer
Rustlings100+ exercises
Exercism RustMentored exercises

เปิด Rust Playground พร้อมโค้ดตัวอย่าง:


📖 Books

Bookคำอธิบาย
Programming RustO’Reilly
Rust in ActionManning
Zero to ProductionWeb Development

🔧 Tools

Toolคำอธิบาย
Rust AnalyzerLanguage Server
cargo-watchAuto rebuild
cargo-expandMacro expansion
cargo-auditSecurity

👉 กลับหน้าแรก

Example Code

โค้ดตัวอย่างที่สามารถ clone และรันได้เลย


📦 วิธีใช้

# Clone repository
git clone https://github.com/premix-kernel/rust-tutorial.git
cd rust-tutorial

# รันตัวอย่าง
cargo run --example <name>

📋 รายการ Examples

Exampleบทคำอธิบาย
hello_world1-4ตัวแปร, ฟังก์ชัน, Control Flow
ownership5Move, Clone, Borrowing, Slices
structs_enums6-7Structs, Methods, Enums, Match
collections8Vec, String, HashMap
error_handling9Result, ?, unwrap_or
generics_traits10Generics, Traits, Bounds
iterators13Closures, map, filter, fold
smart_pointers14Box, Rc, RefCell, Weak
concurrency15Threads, Channels, Mutex
async_await16async/await, join!, spawn
web_server19Axum REST API

🚀 ตัวอย่างคำสั่ง

# Hello World
cargo run --example hello_world

# Ownership (สำคัญมาก!)
cargo run --example ownership

# Web Server (เปิด http://localhost:3000)
cargo run --example web_server

📝 โครงสร้างไฟล์

examples/
├── hello_world.rs       # บทที่ 1-4
├── ownership.rs         # บทที่ 5
├── structs_enums.rs     # บทที่ 6-7
├── collections.rs       # บทที่ 8
├── error_handling.rs    # บทที่ 9
├── generics_traits.rs   # บทที่ 10
├── iterators.rs         # บทที่ 13
├── smart_pointers.rs    # บทที่ 14
├── concurrency.rs       # บทที่ 15
├── async_await.rs       # บทที่ 16
└── web_server.rs        # บทที่ 19

✨ Output ตัวอย่าง

hello_world

🦀 สวัสดี Rust!
Hello, World!

📝 ตัวอย่างตัวแปร:
Name: Rustacean
Age: 25
Is Learning: true

ownership

🦀 Ownership Demo

1️⃣ Move:
s2 = hello

2️⃣ Clone:
s3 = world, s4 = world

3️⃣ Copy:
x = 5, y = 5

👉 กลับหน้า Appendix

แบบฝึกหัด - Exercises

แบบฝึกหัดสำหรับทุกบทในหนังสือ


วิธีใช้

  1. อ่านบทเรียนให้จบก่อน
  2. ลองทำแบบฝึกหัดด้วยตัวเอง
  3. ดูเฉลย/คำตอบหลังจากพยายามแล้ว

Part 1: พื้นฐาน

Part 2: Core Concepts

Part 3: Advanced

Part 4: Concurrency & Advanced

Part 5: Real World


👉 Quiz

แบบฝึกหัด: บทที่ 1 - Getting Started

แบบฝึกหัดที่ 1: ติดตั้งและตรวจสอบ

ติดตั้ง Rust และตรวจสอบว่าติดตั้งสำเร็จ

คำถาม: คำสั่งอะไรใช้ตรวจสอบเวอร์ชัน Rust?

ดูเฉลย
rustc --version
cargo --version

แบบฝึกหัดที่ 2: Hello World

สร้างโปรเจกต์ใหม่และแก้ไขให้แสดง “สวัสดี Rust!”

ขั้นตอน:

  1. สร้างโปรเจกต์ใหม่ชื่อ hello_thai
  2. แก้ไข main.rs ให้แสดง “สวัสดี Rust!”
  3. รันโปรแกรม
ดูเฉลย
cargo new hello_thai
cd hello_thai
// src/main.rs
fn main() {
    println!("สวัสดี Rust!");
}
cargo run

แบบฝึกหัดที่ 3: Cargo คำสั่งพื้นฐาน

เติมคำสั่ง Cargo ให้ถูกต้อง:

  1. สร้างโปรเจกต์ใหม่: cargo ______ my_project
  2. Build โปรเจกต์: cargo ______
  3. รันโปรเจกต์: cargo ______
  4. Check โค้ด (ไม่ build): cargo ______
ดูเฉลย
  1. cargo new my_project
  2. cargo build
  3. cargo run
  4. cargo check

แบบฝึกหัดที่ 4: โครงสร้างโปรเจกต์

หลังจากรัน cargo new my_project จะได้โครงสร้างแบบไหน?

ดูเฉลย
my_project/
├── Cargo.toml
└── src/
    └── main.rs
  • Cargo.toml - ไฟล์ config ของโปรเจกต์
  • src/main.rs - ไฟล์โค้ดหลัก

แบบฝึกหัดที่ 5: แก้ไข Cargo.toml

เพิ่ม dependency rand เวอร์ชัน 0.8 ใน Cargo.toml

ดูเฉลย
[package]
name = "my_project"
version = "0.1.0"
edition = "2024"

[dependencies]
rand = "0.8"

หรือใช้คำสั่ง:

cargo add rand

👉 บทที่ 2

แบบฝึกหัด: บทที่ 2 - Variables & Data Types

แบบฝึกหัดที่ 1: Mutability

แก้ไขโค้ดนี้ให้ทำงานได้:

fn main() {
    let x = 5;
    x = 10;
    println!("{}", x);
}
ดูเฉลย
fn main() {
    let mut x = 5;  // เพิ่ม mut
    x = 10;
    println!("{}", x);
}

แบบฝึกหัดที่ 2: Type Annotation

เติม type ให้ถูกต้อง:

fn main() {
    let a: ____ = 42;
    let b: ____ = 3.14;
    let c: ____ = true;
    let d: ____ = 'A';
    let e: ____ = "Hello";
}
ดูเฉลย
fn main() {
    let a: i32 = 42;
    let b: f64 = 3.14;
    let c: bool = true;
    let d: char = 'A';
    let e: &str = "Hello";
}

แบบฝึกหัดที่ 3: Shadowing

ผลลัพธ์ของโค้ดนี้คืออะไร?

fn main() {
    let x = 5;
    let x = x + 1;
    {
        let x = x * 2;
        println!("inner: {}", x);
    }
    println!("outer: {}", x);
}
ดูเฉลย
inner: 12
outer: 6

อธิบาย:

  • x = 5
  • x = 5 + 1 = 6 (shadowing)
  • ใน block: x = 6 * 2 = 12 (shadowing เฉพาะใน block)
  • เมื่อออกจาก block: x กลับมาเป็น 6

แบบฝึกหัดที่ 4: Tuple และ Array

สร้างโค้ดที่:

  1. สร้าง tuple (i32, f64, bool) ค่า (10, 3.14, true)
  2. Destructure ออกมาเป็น 3 ตัวแปร
  3. สร้าง array ของ i32 ขนาด 5 ค่า [1, 2, 3, 4, 5]
  4. Print ค่าแรกและค่าสุดท้าย
ดูเฉลย
fn main() {
    // Tuple
    let tup: (i32, f64, bool) = (10, 3.14, true);
    let (a, b, c) = tup;
    println!("a={}, b={}, c={}", a, b, c);

    // Array
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    println!("first: {}, last: {}", arr[0], arr[4]);
}

แบบฝึกหัดที่ 5: Constants

ประกาศ constant ที่:

  1. ชื่อ MAX_USERS
  2. Type u32
  3. ค่า 1000
ดูเฉลย
const MAX_USERS: u32 = 1000;

fn main() {
    println!("Max users: {}", MAX_USERS);
}

หมายเหตุ:

  • const ต้องระบุ type เสมอ
  • ใช้ SCREAMING_SNAKE_CASE
  • ต้องกำหนดค่าตอน compile time

👉 บทที่ 3

แบบฝึกหัด: บทที่ 3 - Functions

แบบฝึกหัดที่ 1: Function พื้นฐาน

เขียน function greet ที่:

  • รับ parameter name: &str
  • Print “Hello, {name}!”
ดูเฉลย
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    greet("World");
    greet("Rust");
}

แบบฝึกหัดที่ 2: Return Value

เขียน function add ที่:

  • รับ a: i32 และ b: i32
  • Return ผลรวม
ดูเฉลย
fn add(a: i32, b: i32) -> i32 {
    a + b  // ไม่มี semicolon = return
}

// หรือ
fn add_explicit(a: i32, b: i32) -> i32 {
    return a + b;
}

fn main() {
    let sum = add(5, 3);
    println!("5 + 3 = {}", sum);
}

แบบฝึกหัดที่ 3: Multiple Parameters

เขียน function calculate_rectangle_area ที่:

  • รับ width: f64 และ height: f64
  • Return พื้นที่ (width * height)
ดูเฉลย
fn calculate_rectangle_area(width: f64, height: f64) -> f64 {
    width * height
}

fn main() {
    let area = calculate_rectangle_area(5.0, 3.0);
    println!("Area: {}", area); // 15.0
}

แบบฝึกหัดที่ 4: Early Return

เขียน function is_even ที่:

  • รับ n: i32
  • Return true ถ้าเป็นเลขคู่, false ถ้าเป็นเลขคี่
ดูเฉลย
fn is_even(n: i32) -> bool {
    n % 2 == 0
}

// หรือใช้ early return
fn is_even_v2(n: i32) -> bool {
    if n % 2 == 0 {
        return true;
    }
    false
}

fn main() {
    println!("4 is even: {}", is_even(4)); // true
    println!("7 is even: {}", is_even(7)); // false
}

แบบฝึกหัดที่ 5: Function ที่ Return Tuple

เขียน function min_max ที่:

  • รับ a: i32, b: i32, c: i32
  • Return tuple (min, max)
ดูเฉลย
fn min_max(a: i32, b: i32, c: i32) -> (i32, i32) {
    let min = a.min(b).min(c);
    let max = a.max(b).max(c);
    (min, max)
}

fn main() {
    let (min, max) = min_max(5, 2, 8);
    println!("min: {}, max: {}", min, max); // min: 2, max: 8
}

👉 บทที่ 4

แบบฝึกหัด: บทที่ 4 - Control Flow

แบบฝึกหัดที่ 1: If/Else

เขียน function grade ที่:

  • รับ score: i32
  • Return เกรดตามเกณฑ์:
    • 80+ = “A”
    • 70-79 = “B”
    • 60-69 = “C”
    • 50-59 = “D”
    • ต่ำกว่า 50 = “F”
ดูเฉลย
fn grade(score: i32) -> &'static str {
    if score >= 80 {
        "A"
    } else if score >= 70 {
        "B"
    } else if score >= 60 {
        "C"
    } else if score >= 50 {
        "D"
    } else {
        "F"
    }
}

fn main() {
    println!("85 -> {}", grade(85)); // A
    println!("72 -> {}", grade(72)); // B
    println!("45 -> {}", grade(45)); // F
}

แบบฝึกหัดที่ 2: Loop

เขียน loop ที่:

  1. นับจาก 1 ถึง 5
  2. Print แต่ละเลข
  3. หยุดเมื่อถึง 5
ดูเฉลย
fn main() {
    let mut count = 1;

    loop {
        println!("{}", count);

        if count == 5 {
            break;
        }

        count += 1;
    }
}

แบบฝึกหัดที่ 3: While Loop

เขียนโค้ดที่:

  • ใช้ while loop นับถอยหลังจาก 10 ถึง 1
  • Print “Liftoff!” หลังจบ
ดูเฉลย
fn main() {
    let mut n = 10;

    while n > 0 {
        println!("{}", n);
        n -= 1;
    }

    println!("Liftoff!");
}

แบบฝึกหัดที่ 4: For Loop

เขียนโค้ดที่:

  1. สร้าง array [10, 20, 30, 40, 50]
  2. ใช้ for loop แสดงแต่ละค่าพร้อม index
ดูเฉลย
fn main() {
    let numbers = [10, 20, 30, 40, 50];

    for (index, value) in numbers.iter().enumerate() {
        println!("Index {}: {}", index, value);
    }
}

Output:

Index 0: 10
Index 1: 20
Index 2: 30
Index 3: 40
Index 4: 50

แบบฝึกหัดที่ 5: FizzBuzz

เขียน FizzBuzz:

  • เลข 1 ถึง 20
  • หาร 3 ลงตัว print “Fizz”
  • หาร 5 ลงตัว print “Buzz”
  • หารทั้งสองลงตัว print “FizzBuzz”
  • ไม่ลงตัวทั้งคู่ print เลขนั้น
ดูเฉลย
fn main() {
    for n in 1..=20 {
        if n % 3 == 0 && n % 5 == 0 {
            println!("FizzBuzz");
        } else if n % 3 == 0 {
            println!("Fizz");
        } else if n % 5 == 0 {
            println!("Buzz");
        } else {
            println!("{}", n);
        }
    }
}

👉 บทที่ 5

แบบฝึกหัด: บทที่ 5 - Ownership

แบบฝึกหัดที่ 1: Move

โค้ดนี้มี error อะไร? แก้ไขให้ทำงานได้

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);
}
ดูเฉลย

Error: s1 ถูก move ไป s2 แล้ว

วิธีแก้ 1: ใช้ clone

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}", s1); // ✅ OK
}

วิธีแก้ 2: ใช้ reference

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("{}", s1); // ✅ OK
}

แบบฝึกหัดที่ 2: Function Ownership

โค้ดนี้มี error อะไร? แก้ไขให้ทำงานได้

fn print_string(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    print_string(s);
    println!("{}", s); // Error!
}
ดูเฉลย

Error: s ถูก move เข้า function แล้ว

วิธีแก้: ใช้ reference

fn print_string(s: &String) {  // รับ reference
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    print_string(&s);  // ส่ง reference
    println!("{}", s); // ✅ OK
}

แบบฝึกหัดที่ 3: Mutable Reference

เขียน function append_world ที่:

  • รับ mutable reference ของ String
  • เพิ่ม “ World“ ต่อท้าย
ดูเฉลย
fn append_world(s: &mut String) {
    s.push_str(" World");
}

fn main() {
    let mut greeting = String::from("Hello");
    append_world(&mut greeting);
    println!("{}", greeting); // Hello World
}

แบบฝึกหัดที่ 4: Borrowing Rules

โค้ดนี้ถูกหรือผิด? อธิบาย

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;

    println!("{}, {}, {}", r1, r2, r3);
}
ดูเฉลย

ผิด! ไม่สามารถมี mutable reference พร้อมกับ immutable reference ได้

วิธีแก้:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{}, {}", r1, r2);  // ใช้ r1, r2 ก่อน

    let r3 = &mut s;  // แล้วค่อยสร้าง mutable ref
    println!("{}", r3);
}

แบบฝึกหัดที่ 5: Slice

เขียน function first_word ที่:

  • รับ &String
  • Return slice ของคำแรก (ก่อน space แรก)
ดูเฉลย
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let s = String::from("Hello World");
    let first = first_word(&s);
    println!("First word: {}", first); // Hello
}

👉 บทที่ 6

แบบฝึกหัด: บทที่ 6 - Structs

แบบฝึกหัดที่ 1: สร้าง Struct

สร้าง struct Person ที่มี:

  • name: String
  • age: u32
  • email: String
ดูเฉลย
struct Person {
    name: String,
    age: u32,
    email: String,
}

fn main() {
    let person = Person {
        name: String::from("สมชาย"),
        age: 30,
        email: String::from("somchai@example.com"),
    };

    println!("{} อายุ {} ปี", person.name, person.age);
}

แบบฝึกหัดที่ 2: Methods

เพิ่ม method introduce ให้ struct Person ที่ print “สวัสดี ผมชื่อ {name}”

ดูเฉลย
struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn introduce(&self) {
        println!("สวัสดี ผมชื่อ {}", self.name);
    }
}

fn main() {
    let person = Person {
        name: String::from("สมชาย"),
        age: 30,
    };

    person.introduce(); // สวัสดี ผมชื่อ สมชาย
}

แบบฝึกหัดที่ 3: Associated Function

เพิ่ม associated function new ที่สร้าง Person ใหม่

ดูเฉลย
struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn new(name: String, age: u32) -> Self {
        Self { name, age }
    }
}

fn main() {
    let person = Person::new(String::from("สมชาย"), 30);
    println!("{}, {} ปี", person.name, person.age);
}

แบบฝึกหัดที่ 4: Tuple Struct

สร้าง tuple struct สำหรับ RGB color

ดูเฉลย
struct Color(u8, u8, u8);

fn main() {
    let red = Color(255, 0, 0);
    let green = Color(0, 255, 0);

    println!("Red: RGB({}, {}, {})", red.0, red.1, red.2);
}

แบบฝึกหัดที่ 5: Rectangle Area

สร้าง struct Rectangle ที่มี method:

  • area() คำนวณพื้นที่
  • can_hold(&other) ตรวจสอบว่าใส่ rectangle อื่นได้ไหม
ดูเฉลย
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };

    println!("Area: {}", rect1.area());
    println!("Can hold rect2: {}", rect1.can_hold(&rect2));
}

👉 บทที่ 7

แบบฝึกหัด: บทที่ 7 - Enums & Pattern Matching

แบบฝึกหัดที่ 1: สร้าง Enum

สร้าง enum Direction ที่มี North, South, East, West

ดูเฉลย
enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let dir = Direction::North;

    match dir {
        Direction::North => println!("ไปทางเหนือ"),
        Direction::South => println!("ไปทางใต้"),
        Direction::East => println!("ไปทางตะวันออก"),
        Direction::West => println!("ไปทางตะวันตก"),
    }
}

แบบฝึกหัดที่ 2: Enum with Data

สร้าง enum Message ที่มี:

  • Quit (ไม่มีข้อมูล)
  • Move { x: i32, y: i32 }
  • Write(String)
ดูเฉลย
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

fn main() {
    let msg = Message::Move { x: 10, y: 20 };

    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
        Message::Write(text) => println!("Write: {}", text),
    }
}

แบบฝึกหัดที่ 3: Option

เขียน function divide ที่ return Option<f64>:

  • Return None ถ้าหาร 0
  • Return Some(result) ถ้าหารได้
ดูเฉลย
fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Some(result) => println!("Result: {}", result),
        None => println!("Cannot divide by zero"),
    }

    // หรือใช้ if let
    if let Some(result) = divide(10.0, 0.0) {
        println!("Result: {}", result);
    } else {
        println!("Cannot divide by zero");
    }
}

แบบฝึกหัดที่ 4: Match with Guards

เขียน function ที่รับ Option<i32> และ:

  • ถ้า Some(n) และ n > 0: print “positive”
  • ถ้า Some(n) และ n < 0: print “negative”
  • ถ้า Some(0): print “zero”
  • ถ้า None: print “no value”
ดูเฉลย
fn describe(value: Option<i32>) {
    match value {
        Some(n) if n > 0 => println!("positive"),
        Some(n) if n < 0 => println!("negative"),
        Some(0) => println!("zero"),
        None => println!("no value"),
        _ => unreachable!(),
    }
}

fn main() {
    describe(Some(5));   // positive
    describe(Some(-3));  // negative
    describe(Some(0));   // zero
    describe(None);      // no value
}

แบบฝึกหัดที่ 5: if let

แปลง match นี้เป็น if let:

match some_value {
    Some(x) => println!("{}", x),
    None => (),
}
ดูเฉลย
if let Some(x) = some_value {
    println!("{}", x);
}

👉 บทที่ 8

แบบฝึกหัด: บทที่ 8 - Collections

แบบฝึกหัดที่ 1: Vec

สร้าง Vec และหาผลรวม:

  1. สร้าง Vec ของ i32: [1, 2, 3, 4, 5]
  2. เพิ่ม 6, 7, 8 เข้าไป
  3. หาผลรวมทั้งหมด
ดูเฉลย
fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];

    numbers.push(6);
    numbers.push(7);
    numbers.push(8);

    let sum: i32 = numbers.iter().sum();
    println!("Sum: {}", sum); // 36
}

แบบฝึกหัดที่ 2: String

เขียน function ที่รับ &str และ return String ที่กลับหัวกลับหาง

ดูเฉลย
fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

fn main() {
    let original = "Hello";
    let reversed = reverse_string(original);
    println!("{} -> {}", original, reversed); // Hello -> olleH
}

แบบฝึกหัดที่ 3: HashMap

สร้าง HashMap ที่เก็บคะแนนนักเรียน:

  • “สมชาย” = 85
  • “สมหญิง” = 92
  • หาคะแนนเฉลี่ย
ดูเฉลย
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    scores.insert("สมชาย", 85);
    scores.insert("สมหญิง", 92);

    let total: i32 = scores.values().sum();
    let count = scores.len() as i32;
    let average = total / count;

    println!("Average: {}", average); // 88
}

แบบฝึกหัดที่ 4: Word Count

นับความถี่ของแต่ละคำใน string:

"the quick brown fox jumps over the lazy dog"
ดูเฉลย
use std::collections::HashMap;

fn main() {
    let text = "the quick brown fox jumps over the lazy dog";

    let mut word_count = HashMap::new();

    for word in text.split_whitespace() {
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }

    for (word, count) in &word_count {
        println!("{}: {}", word, count);
    }
}

แบบฝึกหัดที่ 5: Vec กับ Enum

สร้าง Vec ที่เก็บได้หลาย types โดยใช้ Enum:

  • ตัวเลข (i32)
  • ทศนิยม (f64)
  • ข้อความ (String)
ดูเฉลย
enum Value {
    Int(i32),
    Float(f64),
    Text(String),
}

fn main() {
    let data = vec![
        Value::Int(42),
        Value::Float(3.14),
        Value::Text(String::from("Hello")),
    ];

    for item in &data {
        match item {
            Value::Int(n) => println!("Int: {}", n),
            Value::Float(f) => println!("Float: {}", f),
            Value::Text(s) => println!("Text: {}", s),
        }
    }
}

👉 บทที่ 9

แบบฝึกหัด: บทที่ 9 - Error Handling

แบบฝึกหัดที่ 1: Result

เขียน function parse_number ที่:

  • รับ &str
  • Return Result<i32, String>
ดูเฉลย
fn parse_number(s: &str) -> Result<i32, String> {
    s.parse::<i32>()
        .map_err(|_| format!("'{}' is not a valid number", s))
}

fn main() {
    match parse_number("42") {
        Ok(n) => println!("Parsed: {}", n),
        Err(e) => println!("Error: {}", e),
    }

    match parse_number("abc") {
        Ok(n) => println!("Parsed: {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

แบบฝึกหัดที่ 2: ? Operator

แปลงโค้ดนี้ให้ใช้ ?:

fn read_username() -> Result<String, std::io::Error> {
    let file = match std::fs::read_to_string("username.txt") {
        Ok(content) => content,
        Err(e) => return Err(e),
    };
    Ok(file.trim().to_string())
}
ดูเฉลย
fn read_username() -> Result<String, std::io::Error> {
    let content = std::fs::read_to_string("username.txt")?;
    Ok(content.trim().to_string())
}

แบบฝึกหัดที่ 3: unwrap_or

แก้ไขโค้ดให้ใช้ unwrap_or แทน match:

let value = match some_option {
    Some(v) => v,
    None => 0,
};
ดูเฉลย
let value = some_option.unwrap_or(0);

// หรือใช้ unwrap_or_default() ถ้า type implement Default
let value = some_option.unwrap_or_default();

แบบฝึกหัดที่ 4: Custom Error

สร้าง custom error DivisionError สำหรับการหารที่ผิดพลาด

ดูเฉลย
use std::fmt;

#[derive(Debug)]
struct DivisionError {
    message: String,
}

impl fmt::Display for DivisionError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for DivisionError {}

fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
    if b == 0 {
        Err(DivisionError {
            message: String::from("Cannot divide by zero"),
        })
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 0) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

แบบฝึกหัดที่ 5: Chaining Results

เขียน function ที่:

  1. Parse string เป็น number
  2. คูณด้วย 2
  3. Return Result
ดูเฉลย
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse::<i32>().map(|n| n * 2)
}

fn main() {
    println!("{:?}", parse_and_double("5"));  // Ok(10)
    println!("{:?}", parse_and_double("abc")); // Err(...)
}

👉 บทที่ 10

แบบฝึกหัด: บทที่ 10 - Generics & Traits

แบบฝึกหัดที่ 1: Generic Function

เขียน generic function largest ที่หาค่าสูงสุดจาก slice

ดูเฉลย
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    println!("Largest: {}", largest(&numbers));

    let chars = vec!['y', 'm', 'a', 'q'];
    println!("Largest: {}", largest(&chars));
}

แบบฝึกหัดที่ 2: Generic Struct

สร้าง generic struct Pair<T> ที่เก็บ 2 ค่าของ type เดียวกัน

ดูเฉลย
struct Pair<T> {
    first: T,
    second: T,
}

impl<T> Pair<T> {
    fn new(first: T, second: T) -> Self {
        Self { first, second }
    }
}

fn main() {
    let pair = Pair::new(1, 2);
    println!("{}, {}", pair.first, pair.second);

    let pair = Pair::new("hello", "world");
    println!("{}, {}", pair.first, pair.second);
}

แบบฝึกหัดที่ 3: Trait Definition

สร้าง trait Describable ที่มี method describe(&self) -> String

ดูเฉลย
trait Describable {
    fn describe(&self) -> String;
}

struct Person {
    name: String,
    age: u32,
}

impl Describable for Person {
    fn describe(&self) -> String {
        format!("{} อายุ {} ปี", self.name, self.age)
    }
}

struct Product {
    name: String,
    price: f64,
}

impl Describable for Product {
    fn describe(&self) -> String {
        format!("{} ราคา {} บาท", self.name, self.price)
    }
}

fn main() {
    let person = Person { name: String::from("สมชาย"), age: 30 };
    let product = Product { name: String::from("iPhone"), price: 35000.0 };

    println!("{}", person.describe());
    println!("{}", product.describe());
}

แบบฝึกหัดที่ 4: Trait Bounds

เขียน function ที่รับ type ที่ implement ทั้ง Display และ Clone

ดูเฉลย
use std::fmt::Display;

fn print_and_clone<T: Display + Clone>(item: T) -> T {
    println!("{}", item);
    item.clone()
}

fn main() {
    let s = String::from("Hello");
    let cloned = print_and_clone(s);
    println!("Cloned: {}", cloned);
}

แบบฝึกหัดที่ 5: Lifetime

แก้ไขให้ compile ได้:

#![allow(unused)]
fn main() {
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}
ดูเฉลย
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s1 = String::from("long");
    let s2 = String::from("short");

    let result = longest(&s1, &s2);
    println!("Longest: {}", result);
}

👉 บทที่ 11

แบบฝึกหัด: บทที่ 11 - Modules

แบบฝึกหัดที่ 1: Module พื้นฐาน

สร้าง module math ที่มี function add และ multiply

ดูเฉลย
mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn multiply(a: i32, b: i32) -> i32 {
        a * b
    }
}

fn main() {
    println!("5 + 3 = {}", math::add(5, 3));
    println!("5 * 3 = {}", math::multiply(5, 3));
}

แบบฝึกหัดที่ 2: use Statement

ใช้ use เพื่อ import function จาก module

ดูเฉลย
mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}

use math::add;

fn main() {
    println!("5 + 3 = {}", add(5, 3));
}

แบบฝึกหัดที่ 3: Nested Modules

สร้าง module shapes ที่มี submodule circle และ rectangle

ดูเฉลย
mod shapes {
    pub mod circle {
        pub fn area(radius: f64) -> f64 {
            std::f64::consts::PI * radius * radius
        }
    }

    pub mod rectangle {
        pub fn area(width: f64, height: f64) -> f64 {
            width * height
        }
    }
}

use shapes::circle;
use shapes::rectangle;

fn main() {
    println!("Circle area: {}", circle::area(5.0));
    println!("Rectangle area: {}", rectangle::area(4.0, 3.0));
}

แบบฝึกหัดที่ 4: pub struct

สร้าง public struct ใน module แต่บาง field เป็น private

ดูเฉลย
mod user {
    pub struct User {
        pub username: String,
        email: String,  // private
    }

    impl User {
        pub fn new(username: String, email: String) -> Self {
            Self { username, email }
        }

        pub fn email(&self) -> &str {
            &self.email
        }
    }
}

fn main() {
    let user = user::User::new(
        String::from("john"),
        String::from("john@example.com"),
    );

    println!("Username: {}", user.username);
    println!("Email: {}", user.email());
}

แบบฝึกหัดที่ 5: re-export

ใช้ pub use เพื่อ re-export items

ดูเฉลย
mod internal {
    pub mod helpers {
        pub fn format_name(name: &str) -> String {
            format!("Hello, {}!", name)
        }
    }
}

// Re-export ให้ใช้ง่ายขึ้น
pub use internal::helpers::format_name;

fn main() {
    // ใช้ได้โดยตรง
    println!("{}", format_name("World"));
}

👉 บทที่ 12

แบบฝึกหัด: บทที่ 12 - Testing

แบบฝึกหัดที่ 1: Unit Test

เขียน unit test สำหรับ function add:

fn add(a: i32, b: i32) -> i32 {
    a + b
}
ดูเฉลย
fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add_positive() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-2, -3), -5);
    }

    #[test]
    fn test_add_zero() {
        assert_eq!(add(0, 0), 0);
    }
}

แบบฝึกหัดที่ 2: assert! Macros

ใช้ assert ต่างๆ:

  • assert!
  • assert_eq!
  • assert_ne!
ดูเฉลย
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        // assert! - ตรวจสอบ boolean
        assert!(1 + 1 == 2);
        assert!(true);

        // assert_eq! - ตรวจสอบเท่ากัน
        assert_eq!(2 + 2, 4);
        assert_eq!("hello", "hello");

        // assert_ne! - ตรวจสอบไม่เท่ากัน
        assert_ne!(2 + 2, 5);
        assert_ne!("hello", "world");
    }
}
}

แบบฝึกหัดที่ 3: Test Panic

เขียน test ที่ตรวจสอบว่า function panic

ดูเฉลย
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division by zero!");
    }
    a / b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn test_divide_by_zero() {
        divide(10, 0);
    }

    #[test]
    #[should_panic(expected = "Division by zero")]
    fn test_divide_by_zero_message() {
        divide(10, 0);
    }
}

แบบฝึกหัดที่ 4: Test Result

เขียน test ที่ return Result แทน panic

ดูเฉลย
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn test_with_result() -> Result<(), String> {
        let value = "42".parse::<i32>()
            .map_err(|_| String::from("Parse failed"))?;

        if value == 42 {
            Ok(())
        } else {
            Err(String::from("Wrong value"))
        }
    }
}
}

แบบฝึกหัดที่ 5: Ignore Test

เขียน test ที่จะถูกข้ามไป เว้นแต่ระบุให้รัน

ดูเฉลย
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    #[ignore]
    fn expensive_test() {
        // Test ที่ใช้เวลานาน
        std::thread::sleep(std::time::Duration::from_secs(10));
        assert!(true);
    }
}

// รัน ignored tests:
// cargo test -- --ignored

// รันทั้งหมด รวม ignored:
// cargo test -- --include-ignored
}

👉 บทที่ 13

แบบฝึกหัด: บทที่ 13 - Iterators & Closures

แบบฝึกหัดที่ 1: Closure พื้นฐาน

สร้าง closure ที่บวกเลข 2 ตัว

ดูเฉลย
fn main() {
    let add = |a, b| a + b;

    println!("5 + 3 = {}", add(5, 3));

    // หรือระบุ type
    let add_typed = |a: i32, b: i32| -> i32 { a + b };
    println!("5 + 3 = {}", add_typed(5, 3));
}

แบบฝึกหัดที่ 2: Closure ที่ capture

สร้าง closure ที่ capture ตัวแปรจากภายนอก

ดูเฉลย
fn main() {
    let multiplier = 10;

    // Capture by reference
    let multiply = |x| x * multiplier;

    println!("5 * {} = {}", multiplier, multiply(5));

    // Capture by value (move)
    let greeting = String::from("Hello");
    let greet = move |name| format!("{}, {}!", greeting, name);

    println!("{}", greet("World"));
    // println!("{}", greeting); // Error: greeting moved
}

แบบฝึกหัดที่ 3: Iterator Methods

ใช้ map, filter, collect:

  1. สร้าง Vec [1, 2, 3, 4, 5]
  2. กรองเอาเฉพาะเลขคู่
  3. คูณแต่ละตัวด้วย 2
  4. Collect เป็น Vec
ดูเฉลย
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    let result: Vec<i32> = numbers
        .iter()
        .filter(|&x| x % 2 == 0)
        .map(|x| x * 2)
        .collect();

    println!("{:?}", result); // [4, 8]
}

แบบฝึกหัดที่ 4: fold

ใช้ fold หาผลรวมและผลคูณ

ดูเฉลย
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // ผลรวม
    let sum = numbers.iter().fold(0, |acc, x| acc + x);
    println!("Sum: {}", sum); // 15

    // ผลคูณ
    let product = numbers.iter().fold(1, |acc, x| acc * x);
    println!("Product: {}", product); // 120
}

แบบฝึกหัดที่ 5: Custom Iterator

สร้าง iterator ที่นับเลข

ดูเฉลย
struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Self {
        Self { count: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

fn main() {
    let counter = Counter::new(5);

    for num in counter {
        println!("{}", num);
    }
    // 1, 2, 3, 4, 5
}

👉 บทที่ 14

แบบฝึกหัด: บทที่ 14 - Smart Pointers

แบบฝึกหัดที่ 1: Box

ใช้ Box สร้าง recursive data structure (linked list)

ดูเฉลย
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

    // Print list
    fn print_list(list: &List) {
        match list {
            Cons(value, next) => {
                print!("{} -> ", value);
                print_list(next);
            }
            Nil => println!("Nil"),
        }
    }

    print_list(&list); // 1 -> 2 -> 3 -> Nil
}

แบบฝึกหัดที่ 2: Rc

ใช้ Rc สร้าง shared ownership

ดูเฉลย
use std::rc::Rc;

fn main() {
    let data = Rc::new(String::from("Hello"));

    println!("Count after creation: {}", Rc::strong_count(&data));

    {
        let clone1 = Rc::clone(&data);
        println!("Count after clone1: {}", Rc::strong_count(&data));

        let clone2 = Rc::clone(&data);
        println!("Count after clone2: {}", Rc::strong_count(&data));
    }

    println!("Count after block: {}", Rc::strong_count(&data));
}

Output:

Count after creation: 1
Count after clone1: 2
Count after clone2: 3
Count after block: 1

แบบฝึกหัดที่ 3: RefCell

ใช้ RefCell สำหรับ interior mutability

ดูเฉลย
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    println!("Before: {:?}", data.borrow());

    *data.borrow_mut() += 10;

    println!("After: {:?}", data.borrow());
}

แบบฝึกหัดที่ 4: Rc + RefCell

รวม Rc และ RefCell เพื่อ shared mutable data

ดูเฉลย
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let data = Rc::new(RefCell::new(vec![1, 2, 3]));

    let clone1 = Rc::clone(&data);
    let clone2 = Rc::clone(&data);

    // แก้ไขผ่าน clone1
    clone1.borrow_mut().push(4);

    // แก้ไขผ่าน clone2
    clone2.borrow_mut().push(5);

    // ดูผลลัพธ์
    println!("{:?}", data.borrow()); // [1, 2, 3, 4, 5]
}

แบบฝึกหัดที่ 5: Weak

ใช้ Weak เพื่อป้องกัน reference cycle

ดูเฉลย
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    // Set parent (weak reference)
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("Leaf value: {}", leaf.value);

    // Access parent through weak reference
    if let Some(parent) = leaf.parent.borrow().upgrade() {
        println!("Parent value: {}", parent.value);
    }
}

👉 บทที่ 15

แบบฝึกหัด: บทที่ 15 - Concurrency

แบบฝึกหัดที่ 1: Spawn Thread

สร้าง thread ที่ print เลข 1-5

ดูเฉลย
use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..=5 {
            println!("Thread: {}", i);
            thread::sleep(Duration::from_millis(100));
        }
    });

    // Main thread
    for i in 1..=3 {
        println!("Main: {}", i);
        thread::sleep(Duration::from_millis(100));
    }

    handle.join().unwrap();
}

แบบฝึกหัดที่ 2: move Closure

ใช้ move closure เพื่อส่ง data ไป thread

ดูเฉลย
use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    let handle = thread::spawn(move || {
        println!("Data in thread: {:?}", data);
        let sum: i32 = data.iter().sum();
        sum
    });

    let result = handle.join().unwrap();
    println!("Sum: {}", result);
}

แบบฝึกหัดที่ 3: Channel

ใช้ channel ส่งข้อมูลระหว่าง threads

ดูเฉลย
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let messages = vec!["Hello", "from", "thread"];
        for msg in messages {
            tx.send(msg).unwrap();
        }
    });

    for received in rx {
        println!("Received: {}", received);
    }
}

แบบฝึกหัดที่ 4: Arc + Mutex

ใช้ Arc และ Mutex สำหรับ shared mutable state

ดูเฉลย
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    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 || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Count: {}", *counter.lock().unwrap());
}

แบบฝึกหัดที่ 5: Multiple Producers

สร้าง multiple producers ที่ส่งข้อมูลไป receiver เดียว

ดูเฉลย
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    // Producer 1
    let tx1 = tx.clone();
    thread::spawn(move || {
        for i in 1..=3 {
            tx1.send(format!("P1: {}", i)).unwrap();
        }
    });

    // Producer 2
    thread::spawn(move || {
        for i in 1..=3 {
            tx.send(format!("P2: {}", i)).unwrap();
        }
    });

    // Receiver
    for msg in rx {
        println!("{}", msg);
    }
}

👉 บทที่ 16

แบบฝึกหัด: บทที่ 16 - Async/Await

แบบฝึกหัดที่ 1: Async Function

เขียน async function fetch_data ที่ return String

ดูเฉลย
async fn fetch_data() -> String {
    // Simulate network delay
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    String::from("Data from server")
}

#[tokio::main]
async fn main() {
    let data = fetch_data().await;
    println!("{}", data);
}

แบบฝึกหัดที่ 2: Multiple Awaits

รัน 2 async functions ตามลำดับ

ดูเฉลย
async fn step1() -> i32 {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    println!("Step 1 done");
    10
}

async fn step2(value: i32) -> i32 {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    println!("Step 2 done");
    value * 2
}

#[tokio::main]
async fn main() {
    let v1 = step1().await;
    let v2 = step2(v1).await;
    println!("Result: {}", v2);
}

แบบฝึกหัดที่ 3: tokio::join!

รัน multiple futures พร้อมกัน

ดูเฉลย
async fn task_a() -> i32 {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    println!("Task A done");
    1
}

async fn task_b() -> i32 {
    tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
    println!("Task B done");
    2
}

#[tokio::main]
async fn main() {
    let (a, b) = tokio::join!(task_a(), task_b());
    println!("Results: {} + {} = {}", a, b, a + b);
}

แบบฝึกหัดที่ 4: tokio::spawn

Spawn background task

ดูเฉลย
#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        42
    });

    println!("Doing other work...");

    let result = handle.await.unwrap();
    println!("Background task result: {}", result);
}

แบบฝึกหัดที่ 5: Async กับ Result

Handle errors ใน async function

ดูเฉลย
async fn fetch_user(id: i32) -> Result<String, String> {
    if id > 0 {
        Ok(format!("User {}", id))
    } else {
        Err(String::from("Invalid user ID"))
    }
}

#[tokio::main]
async fn main() {
    match fetch_user(1).await {
        Ok(user) => println!("Found: {}", user),
        Err(e) => println!("Error: {}", e),
    }

    // หรือใช้ ?
    async fn get_users() -> Result<(), String> {
        let user = fetch_user(1).await?;
        println!("{}", user);
        Ok(())
    }
}

👉 บทที่ 17

แบบฝึกหัด: บทที่ 17 - Unsafe Rust

แบบฝึกหัดที่ 1: Raw Pointers

สร้างและ dereference raw pointers

ดูเฉลย
fn main() {
    let x = 10;

    // สร้าง raw pointers
    let ptr_const: *const i32 = &x;
    let ptr_mut: *mut i32 = &x as *const i32 as *mut i32;

    unsafe {
        println!("Value via const ptr: {}", *ptr_const);
        println!("Value via mut ptr: {}", *ptr_mut);
    }
}

แบบฝึกหัดที่ 2: Unsafe Function

สร้าง unsafe function ที่ swap ค่า 2 ตัว

ดูเฉลย
unsafe fn swap_raw(a: *mut i32, b: *mut i32) {
    let temp = *a;
    *a = *b;
    *b = temp;
}

fn main() {
    let mut x = 10;
    let mut y = 20;

    println!("Before: x={}, y={}", x, y);

    unsafe {
        swap_raw(&mut x, &mut y);
    }

    println!("After: x={}, y={}", x, y);
}

แบบฝึกหัดที่ 3: Safe Abstraction

ห่อ unsafe code ด้วย safe function

ดูเฉลย
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    let (left, right) = split_at_mut(&mut arr, 2);

    println!("Left: {:?}", left);   // [1, 2]
    println!("Right: {:?}", right); // [3, 4, 5]
}

แบบฝึกหัดที่ 4: Static Mutable

ใช้ static mut (ระวัง!)

ดูเฉลย
static mut COUNTER: i32 = 0;

fn increment() {
    unsafe {
        COUNTER += 1;
    }
}

fn main() {
    increment();
    increment();
    increment();

    unsafe {
        println!("Counter: {}", COUNTER);
    }
}

หมายเหตุ: ไม่แนะนำใน production code! ใช้ Mutex หรือ AtomicI32 แทน


แบบฝึกหัดที่ 5: FFI

เรียก C function จาก Rust (concept)

ดูเฉลย
// ประกาศ external function
extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -5: {}", abs(-5));
    }
}

// สร้าง function ให้ C เรียก
#[no_mangle]
pub extern "C" fn rust_function(x: i32) -> i32 {
    x * 2
}

👉 บทที่ 18

แบบฝึกหัด: บทที่ 18 - Macros

แบบฝึกหัดที่ 1: Declarative Macro

สร้าง macro say_hello! ที่ print “Hello!”

ดูเฉลย
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

fn main() {
    say_hello!();
}

แบบฝึกหัดที่ 2: Macro with Parameters

สร้าง macro greet! ที่รับ name

ดูเฉลย
macro_rules! greet {
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
}

fn main() {
    greet!("World");
    greet!("Rust");
}

แบบฝึกหัดที่ 3: Macro with Repetition

สร้าง macro sum! ที่รับหลายค่าและหาผลรวม

ดูเฉลย
macro_rules! sum {
    ($($x:expr),*) => {
        {
            let mut total = 0;
            $(
                total += $x;
            )*
            total
        }
    };
}

fn main() {
    let result = sum!(1, 2, 3, 4, 5);
    println!("Sum: {}", result); // 15
}

แบบฝึกหัดที่ 4: Create HashMap Macro

สร้าง macro map! คล้าย vec!

ดูเฉลย
macro_rules! map {
    ($($key:expr => $value:expr),* $(,)?) => {
        {
            let mut m = std::collections::HashMap::new();
            $(
                m.insert($key, $value);
            )*
            m
        }
    };
}

fn main() {
    let scores = map! {
        "Alice" => 100,
        "Bob" => 85,
        "Charlie" => 90,
    };

    println!("{:?}", scores);
}

แบบฝึกหัดที่ 5: dbg! Usage

ใช้ dbg! macro สำหรับ debugging

ดูเฉลย
fn factorial(n: u32) -> u32 {
    if dbg!(n <= 1) {
        dbg!(1)
    } else {
        dbg!(n * factorial(n - 1))
    }
}

fn main() {
    let result = dbg!(factorial(4));
    println!("Result: {}", result);
}

// Output จะแสดง file:line และค่าของแต่ละ expression

👉 บทที่ 19

แบบฝึกหัด: บทที่ 19 - Web Development

แบบฝึกหัดที่ 1: Hello World Server

สร้าง Axum server ที่ return “Hello, World!”

ดูเฉลย
use axum::{Router, routing::get};

async fn hello() -> &'static str {
    "Hello, World!"
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(hello));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    println!("Server running on http://127.0.0.1:3000");
    axum::serve(listener, app).await.unwrap();
}

แบบฝึกหัดที่ 2: Path Parameters

สร้าง route /hello/:name ที่ทักทายตามชื่อ

ดูเฉลย
use axum::{Router, routing::get, extract::Path};

async fn greet(Path(name): Path<String>) -> String {
    format!("Hello, {}!", name)
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/hello/:name", get(greet));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

แบบฝึกหัดที่ 3: JSON Response

Return JSON response

ดูเฉลย
use axum::{Router, routing::get, Json};
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

async fn get_user() -> Json<User> {
    Json(User {
        id: 1,
        name: "John Doe".to_string(),
        email: "john@example.com".to_string(),
    })
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/user", get(get_user));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

แบบฝึกหัดที่ 4: POST with JSON Body

รับ JSON body และ return response

ดูเฉลย
use axum::{Router, routing::post, Json};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct UserResponse {
    id: u32,
    name: String,
    email: String,
    message: String,
}

async fn create_user(Json(payload): Json<CreateUser>) -> Json<UserResponse> {
    Json(UserResponse {
        id: 1,
        name: payload.name,
        email: payload.email,
        message: "User created successfully".to_string(),
    })
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/users", post(create_user));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

แบบฝึกหัดที่ 5: Multiple Routes

สร้าง REST API ที่มีหลาย routes

ดูเฉลย
use axum::{
    Router,
    routing::{get, post, delete},
    extract::Path,
    Json,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct Item {
    id: u32,
    name: String,
}

async fn list_items() -> Json<Vec<Item>> {
    Json(vec![
        Item { id: 1, name: "Item 1".to_string() },
        Item { id: 2, name: "Item 2".to_string() },
    ])
}

async fn get_item(Path(id): Path<u32>) -> Json<Item> {
    Json(Item { id, name: format!("Item {}", id) })
}

#[derive(Deserialize)]
struct CreateItem {
    name: String,
}

async fn create_item(Json(payload): Json<CreateItem>) -> Json<Item> {
    Json(Item { id: 3, name: payload.name })
}

async fn delete_item(Path(id): Path<u32>) -> String {
    format!("Deleted item {}", id)
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/items", get(list_items).post(create_item))
        .route("/items/:id", get(get_item).delete(delete_item));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    println!("API running on http://127.0.0.1:3000");
    axum::serve(listener, app).await.unwrap();
}

👉 บทที่ 20

แบบฝึกหัด: บทที่ 20 - Final Project

แบบฝึกหัดที่ 1: Project Setup

สร้าง project structure สำหรับ CLI todo app

ดูเฉลย
cargo new todo_app
cd todo_app
cargo add clap --features derive
cargo add serde --features derive
cargo add serde_json
todo_app/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── todo.rs
│   └── storage.rs
└── data/
    └── todos.json

แบบฝึกหัดที่ 2: Data Structure

สร้าง Todo struct

ดูเฉลย
// src/todo.rs
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Todo {
    pub id: u32,
    pub title: String,
    pub completed: bool,
}

impl Todo {
    pub fn new(id: u32, title: String) -> Self {
        Self {
            id,
            title,
            completed: false,
        }
    }

    pub fn toggle(&mut self) {
        self.completed = !self.completed;
    }
}

แบบฝึกหัดที่ 3: Storage Layer

สร้าง module สำหรับ save/load todos

ดูเฉลย
// src/storage.rs
use crate::todo::Todo;
use std::fs;
use std::path::Path;

const FILE_PATH: &str = "data/todos.json";

pub fn load_todos() -> Vec<Todo> {
    if Path::new(FILE_PATH).exists() {
        let data = fs::read_to_string(FILE_PATH).unwrap_or_default();
        serde_json::from_str(&data).unwrap_or_default()
    } else {
        vec![]
    }
}

pub fn save_todos(todos: &[Todo]) -> std::io::Result<()> {
    fs::create_dir_all("data")?;
    let json = serde_json::to_string_pretty(todos)?;
    fs::write(FILE_PATH, json)
}

แบบฝึกหัดที่ 4: CLI Interface

ใช้ clap สร้าง command line interface

ดูเฉลย
// src/main.rs
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "todo")]
#[command(about = "A simple todo app")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Add a new todo
    Add { title: String },
    /// List all todos
    List,
    /// Toggle todo completion
    Toggle { id: u32 },
    /// Delete a todo
    Delete { id: u32 },
}

fn main() {
    let cli = Cli::parse();

    match cli.command {
        Commands::Add { title } => {
            println!("Adding: {}", title);
        }
        Commands::List => {
            println!("Listing todos...");
        }
        Commands::Toggle { id } => {
            println!("Toggling: {}", id);
        }
        Commands::Delete { id } => {
            println!("Deleting: {}", id);
        }
    }
}

แบบฝึกหัดที่ 5: Complete Implementation

รวมทุกอย่างเข้าด้วยกัน

ดูเฉลย
// src/main.rs
mod storage;
mod todo;

use clap::{Parser, Subcommand};
use storage::{load_todos, save_todos};
use todo::Todo;

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Add { title: String },
    List,
    Toggle { id: u32 },
    Delete { id: u32 },
}

fn main() {
    let cli = Cli::parse();
    let mut todos = load_todos();

    match cli.command {
        Commands::Add { title } => {
            let id = todos.len() as u32 + 1;
            todos.push(Todo::new(id, title.clone()));
            save_todos(&todos).unwrap();
            println!("✅ Added: {}", title);
        }
        Commands::List => {
            if todos.is_empty() {
                println!("No todos yet!");
            } else {
                for todo in &todos {
                    let status = if todo.completed { "✓" } else { " " };
                    println!("[{}] {} - {}", status, todo.id, todo.title);
                }
            }
        }
        Commands::Toggle { id } => {
            if let Some(todo) = todos.iter_mut().find(|t| t.id == id) {
                todo.toggle();
                save_todos(&todos).unwrap();
                println!("Toggled: {}", todo.title);
            } else {
                println!("Todo not found");
            }
        }
        Commands::Delete { id } => {
            let len = todos.len();
            todos.retain(|t| t.id != id);
            if todos.len() < len {
                save_todos(&todos).unwrap();
                println!("Deleted todo {}", id);
            } else {
                println!("Todo not found");
            }
        }
    }
}

Usage:

cargo run -- add "Learn Rust"
cargo run -- list
cargo run -- toggle 1
cargo run -- delete 1

🎉 ยินดีด้วย! คุณเรียนจบครบทุกบทแล้ว!

👉 Quiz

Quiz

ทดสอบความรู้หลังจากเรียนแต่ละบท


วิธีใช้

  1. ตอบคำถามด้วยตัวเองก่อน
  2. คลิกดูเฉลยหลังจากตอบแล้ว
  3. ถ้าตอบผิด กลับไปอ่านบทเรียนอีกครั้ง

Part 1: พื้นฐาน

Part 2: Core Concepts

Part 3: Advanced

Part 4: Concurrency & Advanced

Part 5: Real World


👉 Resources

Quiz: บทที่ 1 - Getting Started

คำถามที่ 1

คำสั่งใดใช้ตรวจสอบว่า Rust ติดตั้งสำเร็จ?

A. rust --version
B. rustc --version
C. cargo version
D. rust-check

ดูเฉลย

B. rustc --version

rustc คือ Rust compiler ส่วน cargo --version ก็ใช้ได้เช่นกัน


คำถามที่ 2

Cargo คืออะไร?

A. Text editor สำหรับ Rust
B. Rust compiler
C. Package manager และ build tool
D. Testing framework

ดูเฉลย

C. Package manager และ build tool

Cargo ทำหน้าที่:

  • จัดการ dependencies
  • Build โปรเจกต์
  • รัน tests
  • สร้าง documentation

คำถามที่ 3

คำสั่งใดสร้างโปรเจกต์ Rust ใหม่?

A. cargo init project
B. cargo new project
C. rustc new project
D. rust create project

ดูเฉลย

B. cargo new project

  • cargo new สร้างโฟลเดอร์ใหม่
  • cargo init สร้างในโฟลเดอร์ปัจจุบัน

คำถามที่ 4

ไฟล์ใดเก็บ dependencies ของโปรเจกต์?

A. main.rs
B. Cargo.lock
C. Cargo.toml
D. package.json

ดูเฉลย

C. Cargo.toml

  • Cargo.toml = ประกาศ dependencies
  • Cargo.lock = lock versions ที่ใช้จริง

คำถามที่ 5

คำสั่งใดตรวจสอบโค้ดโดยไม่ build?

A. cargo run
B. cargo build
C. cargo test
D. cargo check

ดูเฉลย

D. cargo check

cargo check เร็วกว่า cargo build เพราะไม่สร้าง binary


👉 Quiz บทที่ 2

Quiz: บทที่ 2 - Variables & Data Types

คำถามที่ 1

ตัวแปรใน Rust เป็น mutable หรือ immutable โดย default?

A. Mutable
B. Immutable
C. ขึ้นอยู่กับ type
D. ขึ้นอยู่กับ scope

ดูเฉลย

B. Immutable

ต้องใช้ mut เพื่อทำให้เป็น mutable:

let mut x = 5;

คำถามที่ 2

type ใดเป็น default สำหรับ integer?

A. i8
B. i16
C. i32
D. i64

ดูเฉลย

C. i32

let x = 42; // i32 by default

คำถามที่ 3

Shadowing คืออะไร?

A. การลบตัวแปร
B. การประกาศตัวแปรชื่อเดียวกันใหม่
C. การเปลี่ยน type ของตัวแปร
D. การสร้าง reference

ดูเฉลย

B. การประกาศตัวแปรชื่อเดียวกันใหม่

let x = 5;
let x = x + 1; // shadow

คำถามที่ 4

const ต่างจาก let อย่างไร?

A. const เปลี่ยนค่าได้
B. const ต้องระบุ type
C. const ใช้ใน function เท่านั้น
D. ไม่มีความแตกต่าง

ดูเฉลย

B. const ต้องระบุ type

const MAX: u32 = 100; // ต้องระบุ type
let x = 100;          // type inference ได้

คำถามที่ 5

ผลลัพธ์ของ code นี้คืออะไร?

let x = 5;
let x = x + 1;
{
    let x = x * 2;
}
println!("{}", x);

A. 5
B. 6
C. 12
D. Error

ดูเฉลย

B. 6

  • x = 5
  • x = 5 + 1 = 6 (shadow)
  • ใน block: x = 12 (shadow เฉพาะ block)
  • หลัง block: x กลับมาเป็น 6

👉 Quiz บทที่ 3

Quiz: บทที่ 3 - Functions

คำถามที่ 1

Syntax ที่ถูกต้องสำหรับ function signature?

A. function add(a: i32, b: i32) -> i32
B. fn add(a: i32, b: i32) -> i32
C. def add(a: i32, b: i32) -> i32
D. func add(a: i32, b: i32) -> i32

ดูเฉลย

B. fn add(a: i32, b: i32) -> i32

Rust ใช้ fn keyword สำหรับ function


คำถามที่ 2

วิธีใดไม่ถูกต้องสำหรับ return ค่า?

A. return x;
B. x (ไม่มี semicolon)
C. x; (มี semicolon)
D. ทั้ง A และ B ถูกต้อง

ดูเฉลย

C. x; (มี semicolon)

  • x = expression = return ค่า
  • x; = statement = return ()

คำถามที่ 3

function นี้ return type อะไร?

fn greet() {
    println!("Hello");
}

A. String
B. ()
C. void
D. None

ดูเฉลย

B. ()

Unit type () คือ empty tuple หมายถึง “ไม่มีค่า return”


คำถามที่ 4

Parameter ต้องระบุ type หรือไม่?

A. ต้องระบุเสมอ
B. ไม่ต้อง compiler จะ infer
C. ระบุเฉพาะ reference
D. ระบุเฉพาะ generic

ดูเฉลย

A. ต้องระบุเสมอ

fn add(a: i32, b: i32) -> i32 { // ต้องระบุ
    a + b                        // return type ก็ต้องระบุ
}

คำถามที่ 5

ผลลัพธ์ของ code นี้?

fn double(x: i32) -> i32 {
    x * 2
}
println!("{}", double(5));

A. 5
B. 10
C. Error
D. 2

ดูเฉลย

B. 10

double(5) = 5 * 2 = 10


👉 Quiz บทที่ 4

Quiz: บทที่ 4 - Control Flow

คำถามที่ 1

if expression ใน Rust ต้องมี condition เป็น type อะไร?

A. i32
B. bool
C. อะไรก็ได้
D. 0 หรือ 1

ดูเฉลย

B. bool

if true { }  // ✅
if 1 { }     // ❌ Error
```text

</details>

---

# คำถามที่ 2

loop ใดรันไม่รู้จบ?

A. `for`  
B. `while`  
C. `loop`  
D. ทุกแบบ

<details>
<summary>ดูเฉลย</summary>

**C. `loop`**

`loop` รันตลอดจนกว่าจะ `break`

```rust,ignore
loop {
    break; // ออกจาก loop
}

คำถามที่ 3

for i in 0..5 วนกี่รอบ?

A. 4 รอบ
B. 5 รอบ
C. 6 รอบ
D. Error

ดูเฉลย

B. 5 รอบ

0..5 = 0, 1, 2, 3, 4 (exclusive 5) 0..=5 = 0, 1, 2, 3, 4, 5 (inclusive)


คำถามที่ 4

break สามารถ return ค่าได้ไหม?

A. ได้ ใน loop เท่านั้น
B. ได้ ทุก loop
C. ไม่ได้
D. ได้ แต่ต้องเป็น return

ดูเฉลย

A. ได้ ใน loop เท่านั้น

let result = loop {
    break 42;
};
```text

</details>

---

# คำถามที่ 5

`continue` ทำอะไร?

A. หยุด loop  
B. ข้ามไป iteration ถัดไป  
C. Return ค่า  
D. ไม่มี effect

<details>
<summary>ดูเฉลย</summary>

**B. ข้ามไป iteration ถัดไป**

```rust,ignore
for i in 0..5 {
    if i == 2 { continue; }
    println!("{}", i); // 0, 1, 3, 4
}

👉 Quiz บทที่ 5

Quiz: บทที่ 5 - Ownership

คำถามที่ 1

กฎ Ownership ข้อใดถูกต้อง?

A. ค่าหนึ่งมีได้หลาย owner
B. ค่าหนึ่งมีได้ owner เดียว
C. Owner ไม่สามารถ transfer ได้
D. ค่าไม่ถูก drop เมื่อ owner ออกจาก scope

ดูเฉลย

B. ค่าหนึ่งมีได้ owner เดียว

กฎ 3 ข้อ:

  1. ทุกค่ามี owner หนึ่งเดียว
  2. มีได้แค่ owner เดียว
  3. เมื่อ owner ออกจาก scope ค่าถูก drop

คำถามที่ 2

code นี้ถูกหรือผิด?

let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1);

A. ถูก
B. ผิด - s1 ถูก move
C. ผิด - syntax error
D. ผิด - type mismatch

ดูเฉลย

B. ผิด - s1 ถูก move

s1 ถูก move ไป s2 จึงใช้ไม่ได้


คำถามที่ 3

& หมายถึงอะไร?

A. Pointer
B. Reference (borrow)
C. Copy
D. Move

ดูเฉลย

B. Reference (borrow)

let s = String::from("hello");
let r = &s; // borrow แบบ immutable

คำถามที่ 4

immutable reference มีได้กี่อัน?

A. 1 อัน
B. หลายอัน
C. ขึ้นอยู่กับ type
D. 0 อัน

ดูเฉลย

B. หลายอัน

let r1 = &s;
let r2 = &s; // ✅ OK

แต่ mutable reference มีได้ 1 อัน


คำถามที่ 5

Slice &s[0..5] ทำอะไร?

A. Copy ค่า
B. สร้าง reference ไปยังส่วนหนึ่ง
C. ลบข้อมูล
D. สร้าง String ใหม่

ดูเฉลย

B. สร้าง reference ไปยังส่วนหนึ่ง

Slice คือ view เข้าไปในข้อมูลเดิม ไม่ copy


👉 Quiz บทที่ 6

Quiz: บทที่ 6 - Structs

คำถามที่ 1

keyword ใดใช้สร้าง struct?

A. class
B. struct
C. type
D. data

ดูเฉลย

B. struct

struct User {
    name: String,
    age: u32,
}

คำถามที่ 2

method ต่างจาก function อย่างไร?

A. ไม่ต่างกัน
B. method มี self parameter
C. method ไม่มี return
D. function ต้องอยู่ใน impl

ดูเฉลย

B. method มี self parameter

impl User {
    fn greet(&self) { } // method มี self
    fn new() -> Self { } // associated function ไม่มี self
}

คำถามที่ 3

&self หมายถึงอะไร?

A. mutable reference to self
B. immutable reference to self
C. owned self
D. static method

ดูเฉลย

B. immutable reference to self

  • &self = immutable borrow
  • &mut self = mutable borrow
  • self = take ownership

คำถามที่ 4

Tuple struct คืออะไร?

A. Struct ที่มี tuple field
B. Struct ที่ไม่มีชื่อ field
C. Struct ที่เป็น generic
D. Struct ที่มี method

ดูเฉลย

B. Struct ที่ไม่มีชื่อ field

struct Color(u8, u8, u8);
let red = Color(255, 0, 0);
println!("{}", red.0); // access by index
```text

</details>

---

# คำถามที่ 5

`Self` ต่างจาก `self` อย่างไร?

A. ไม่ต่าง  
B. `Self` คือ type, `self` คือ instance  
C. `Self` คือ instance  
D. `self` คือ type

<details>
<summary>ดูเฉลย</summary>

**B. `Self` คือ type, `self` คือ instance**

```rust,ignore
impl User {
    fn new() -> Self { // Self = User type
        Self { name: String::new() }
    }

    fn greet(&self) { } // self = instance
}

👉 Quiz บทที่ 7

Quiz: บทที่ 7 - Enums & Pattern Matching

คำถาม 7.1

Option<T> มี variants อะไรบ้าง?

A. Ok และ Err
B. Some และ None
C. True และ False
D. Value และ Null

ดูเฉลย

B. Some และ None

enum Option<T> {
    Some(T),
    None,
}

คำถาม 7.2

if let ใช้แทนอะไร?

A. if else
B. match ที่มี pattern เดียว
C. loop
D. while

ดูเฉลย

B. match ที่มี pattern เดียว

// แทนที่จะเขียน
match value {
    Some(x) => println!("{}", x),
    None => (),
}

// ใช้ if let
if let Some(x) = value {
    println!("{}", x);
}

คำถาม 7.3

Enum variant สามารถมี data ได้ไหม?

A. ไม่ได้
B. ได้ แต่ต้องมี type เดียว
C. ได้ หลาย types
D. ได้ แต่ต้องเป็น struct

ดูเฉลย

C. ได้ หลาย types

enum Message {
    Quit,                       // ไม่มี data
    Move { x: i32, y: i32 },   // struct-like
    Write(String),              // tuple-like
    ChangeColor(i32, i32, i32), // multiple values
}

คำถาม 7.4

match ต้อง exhaustive หมายความว่าอะไร?

A. ต้องมี default case
B. ต้อง handle ทุก variants
C. ต้องมี guard
D. ต้อง return ค่า

ดูเฉลย

B. ต้อง handle ทุก variants

ถ้า enum มี 3 variants ต้อง match ทั้ง 3 หรือใช้ _ catch-all


คำถาม 7.5

_ ใน match หมายถึงอะไร?

A. Null value
B. Error case
C. Catch-all pattern
D. Empty tuple

ดูเฉลย

C. Catch-all pattern

match value {
    1 => println!("one"),
    2 => println!("two"),
    _ => println!("other"), // catch all
}

👉 Quiz บทที่ 8

Quiz: บทที่ 8 - Collections

คำถาม 8.1

Vec::new() สร้างอะไร?

A. Fixed array
B. Empty vector
C. Linked list
D. HashMap

ดูเฉลย

B. Empty vector

let v: Vec<i32> = Vec::new();
// หรือ
let v = vec![1, 2, 3];
```text

</details>

---

# คำถาม 8.2

`String` ต่างจาก `&str` อย่างไร?

A. ไม่ต่าง  
B. `String` เป็น owned, `&str` เป็น borrowed  
C. `String` เร็วกว่า  
D. `&str` ใหญ่กว่า

<details>
<summary>ดูเฉลย</summary>

**B. `String` เป็น owned, `&str` เป็น borrowed**

- `String` = เก็บบน heap, เป็นเจ้าของ, แก้ไขได้
- `&str` = reference ไป string slice

</details>

---

# คำถาม 8.3

`HashMap::get()` return อะไร?

A. ค่าตรงๆ  
B. `Option<&V>`  
C. `Result<V, E>`  
D. `&V`

<details>
<summary>ดูเฉลย</summary>

**B. `Option<&V>`**

```rust,ignore
let value = map.get("key"); // Option<&V>
```text

Return `Some(&value)` ถ้าพบ, `None` ถ้าไม่พบ

</details>

---

# คำถาม 8.4

`vec.push(value)` ทำอะไร?

A. เพิ่มตัวแรก  
B. เพิ่มตัวสุดท้าย  
C. ลบตัวแรก  
D. ลบตัวสุดท้าย

<details>
<summary>ดูเฉลย</summary>

**B. เพิ่มตัวสุดท้าย**

```rust,ignore
let mut v = vec![1, 2, 3];
v.push(4); // [1, 2, 3, 4]
```text

</details>

---

# คำถาม 8.5

`entry().or_insert()` ใช้ทำอะไร?

A. ลบค่าถ้ามี  
B. แทรกค่าถ้ายังไม่มี  
C. อัปเดตค่าเสมอ  
D. ดึงค่าออกมา

<details>
<summary>ดูเฉลย</summary>

**B. แทรกค่าถ้ายังไม่มี**

```rust,ignore
let count = map.entry("key").or_insert(0);
*count += 1;

👉 Quiz บทที่ 9

Quiz: บทที่ 9 - Error Handling

คำถาม 9.1

Result<T, E> มี variants อะไร?

A. Some และ None
B. Ok และ Err
C. Success และ Failure
D. Value และ Error

ดูเฉลย

B. Ok และ Err

enum Result<T, E> {
    Ok(T),
    Err(E),
}

คำถาม 9.2

? operator ทำอะไร?

A. Return None
B. Unwrap หรือ return error
C. Panic
D. Print error

ดูเฉลย

B. Unwrap หรือ return error

fn read_file() -> Result<String, Error> {
    let content = std::fs::read_to_string("file.txt")?;
    Ok(content)
}

ถ้า error จะ return Err ออกจาก function ทันที


คำถาม 9.3

panic! ใช้เมื่อไหร่?

A. ทุก error
B. Unrecoverable errors
C. Network errors
D. User input errors

ดูเฉลย

B. Unrecoverable errors

panic! ใช้เมื่อโปรแกรมไม่สามารถดำเนินต่อได้อย่างปลอดภัย


คำถาม 9.4

unwrap() ต่างจาก expect() อย่างไร?

A. ไม่ต่าง
B. expect มี custom message
C. unwrap ไม่ panic
D. expect return Option

ดูเฉลย

B. expect มี custom message

let x = some_option.unwrap();       // generic message
let x = some_option.expect("msg"); // custom message
```text

</details>

---

# คำถาม 9.5

`unwrap_or()` ทำอะไร?

A. Panic ถ้า None  
B. Return ค่า default ถ้า None  
C. Return Result  
D. แปลงเป็น Option

<details>
<summary>ดูเฉลย</summary>

**B. Return ค่า default ถ้า None**

```rust,ignore
let x = some_option.unwrap_or(0); // ใช้ 0 ถ้า None

👉 Quiz บทที่ 10

Quiz: บทที่ 10 - Generics & Traits

คำถาม 10.1

<T> หมายถึงอะไร?

A. Template
B. Type parameter (generic)
C. Tuple
D. Trait

ดูเฉลย

B. Type parameter (generic)

fn largest<T: PartialOrd>(list: &[T]) -> &T { ... }
```text

</details>

---

# คำถาม 10.2

Lifetime `'a` ใช้ทำอะไร?

A. กำหนดเวลารัน  
B. บอก compiler ว่า reference อยู่นานแค่ไหน  
C. สร้าง async  
D. กำหนด thread

<details>
<summary>ดูเฉลย</summary>

**B. บอก compiler ว่า reference อยู่นานแค่ไหน**

```rust,ignore
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
```text

</details>

---

# คำถาม 10.3

Trait คล้ายกับอะไรในภาษาอื่น?

A. Class  
B. Interface  
C. Struct  
D. Enum

<details>
<summary>ดูเฉลย</summary>

**B. Interface**

Trait define behavior ที่ types ต้อง implement

```rust,ignore
trait Summary {
    fn summarize(&self) -> String;
}

คำถาม 10.4

impl Trait ใช้ทำอะไร?

A. สร้าง trait
B. Implement methods
C. Return type ที่ implement trait
D. Delete trait

ดูเฉลย

C. Return type ที่ implement trait

fn returns_summarizable() -> impl Summary {
    // return any type that implements Summary
}

คำถาม 10.5

where clause ใช้ทำอะไร?

A. Filter data
B. Specify trait bounds
C. Create loops
D. Handle errors

ดูเฉลย

B. Specify trait bounds

fn some_function<T, U>(t: &T, u: &U)
where
    T: Display + Clone,
    U: Clone + Debug,
{ ... }

👉 Quiz บทที่ 11

Quiz: บทที่ 11 - Modules

คำถาม 11.1

pub keyword ทำอะไร?

A. ทำให้ private
B. ทำให้ public
C. สร้าง package
D. import module

ดูเฉลย

B. ทำให้ public

pub fn public_function() { }
fn private_function() { } // default private
```text

</details>

---

# คำถาม 11.2

`use crate::module::function` หมายถึง?

A. Import จาก external crate  
B. Import จาก current crate  
C. Export function  
D. Delete function

<details>
<summary>ดูเฉลย</summary>

**B. Import จาก current crate**

- `crate::` = current crate root
- `super::` = parent module
- `self::` = current module

</details>

---

# คำถาม 11.3

`mod.rs` ใช้ทำอะไร?

A. Main file  
B. Entry point ของ module  
C. Test file  
D. Config file

<details>
<summary>ดูเฉลย</summary>

**B. Entry point ของ module**

```text
src/
├── main.rs
└── my_module/
    ├── mod.rs      # entry point
    └── submodule.rs
```text

</details>

---

# คำถาม 11.4

`pub use` ทำอะไร?

A. Private re-export  
B. Public re-export  
C. Delete export  
D. Test export

<details>
<summary>ดูเฉลย</summary>

**B. Public re-export**

```rust,ignore
pub use internal::helper::function;
// ตอนนี้ใช้ crate::function ได้เลย

คำถาม 11.5

Crate กับ Package ต่างกันอย่างไร?

A. ไม่ต่าง
B. Package มีได้หลาย crates
C. Crate ใหญ่กว่า
D. Crate มี Cargo.toml

ดูเฉลย

B. Package มีได้หลาย crates

  • Package = มี Cargo.toml
  • Crate = compilation unit (lib หรือ bin)

👉 Quiz บทที่ 12

Quiz: บทที่ 12 - Testing

คำถาม 12.1

Attribute ใดใช้ mark test function?

A. #[test]
B. #[unit_test]
C. @test
D. test!

ดูเฉลย

A. #[test]

#[test]
fn test_add() {
    assert_eq!(2 + 2, 4);
}

คำถาม 12.2

#[should_panic] ใช้ทำอะไร?

A. ทำให้ test panic
B. Test ผ่านถ้า function panic
C. ข้าม test
D. Print panic message

ดูเฉลย

B. Test ผ่านถ้า function panic

#[test]
#[should_panic]
fn test_divide_by_zero() {
    divide(1, 0);
}

คำถาม 12.3

Integration tests อยู่ที่ไหน?

A. src/ folder
B. tests/ folder
C. lib.rs
D. main.rs

ดูเฉลย

B. tests/ folder

project/
├── src/
│   └── lib.rs
└── tests/
    └── integration_test.rs
```text

</details>

---

## คำถาม 12.4

`#[ignore]` ใช้ทำอะไร?

A. Delete test  
B. Skip test by default  
C. Run only this test  
D. Mark as failed

<details>
<summary>ดูเฉลย</summary>

**B. Skip test by default**

```rust,ignore
#[test]
#[ignore]
fn expensive_test() { ... }

// รัน: cargo test -- --ignored
```text

</details>

---

## คำถาม 12.5

`assert_eq!(a, b)` ต่างจาก `assert!(a == b)` อย่างไร?

A. ไม่ต่าง  
B. `assert_eq!` แสดง values เมื่อ fail  
C. `assert!` เร็วกว่า  
D. `assert_eq!` ใช้ได้เฉพาะ numbers

<details>
<summary>ดูเฉลย</summary>

**B. `assert_eq!` แสดง values เมื่อ fail**

```text
left: 5
right: 6

👉 Quiz บทที่ 13

Quiz: บทที่ 13 - Iterators & Closures

คำถาม 13.1

Closure คืออะไร?

A. Function ที่ปิด scope
B. Anonymous function ที่ capture environment
C. Loop ที่หยุดเอง
D. Error handler

ดูเฉลย

B. Anonymous function ที่ capture environment

let x = 10;
let add_x = |y| y + x; // capture x
```text

</details>

---

# คำถาม 13.2

`.collect()` ทำอะไร?

A. รวม iterators  
B. แปลง iterator เป็น collection  
C. นับ items  
D. กรอง items

<details>
<summary>ดูเฉลย</summary>

**B. แปลง iterator เป็น collection**

```rust,ignore
let v: Vec<i32> = (1..5).collect();
```text

</details>

---

# คำถาม 13.3

`Fn`, `FnMut`, `FnOnce` ต่างกันอย่างไร?

A. ไม่ต่าง  
B. วิธี capture environment  
C. Return type  
D. Parameter count

<details>
<summary>ดูเฉลย</summary>

**B. วิธี capture environment**

- `Fn` - borrow (&)
- `FnMut` - borrow mutably (&mut)
- `FnOnce` - take ownership

</details>

---

# คำถาม 13.4

`.map()` ทำอะไร?

A. Filter elements  
B. Transform each element  
C. Sum all elements  
D. Find first match

<details>
<summary>ดูเฉลย</summary>

**B. Transform each element**

```rust,ignore
let doubled: Vec<i32> = vec![1, 2, 3]
    .iter()
    .map(|x| x * 2)
    .collect(); // [2, 4, 6]
```text

</details>

---

# คำถาม 13.5

Iterators ใน Rust เป็น lazy หมายความว่า?

A. ทำงานช้า  
B. ไม่ทำงานจนกว่าจะ consume  
C. ใช้ memory เยอะ  
D. ต้องใช้ async

<details>
<summary>ดูเฉลย</summary>

**B. ไม่ทำงานจนกว่าจะ consume**

```rust
let iter = (1..1000).map(|x| x * 2);
// ยังไม่ทำอะไรเลย จนกว่าจะ .collect() หรือ .for_each()

👉 Quiz บทที่ 14

Quiz: บทที่ 14 - Smart Pointers

คำถาม 14.1

Box<T> ใช้เก็บข้อมูลที่ไหน?

A. Stack
B. Heap
C. Static memory
D. Register

ดูเฉลย

B. Heap

let b = Box::new(5); // เก็บ 5 บน heap
```text

</details>

---

# คำถาม 14.2

`Rc<T>` ต่างจาก `Box<T>` อย่างไร?

A. ไม่ต่าง  
B. `Rc` มีได้หลาย owners  
C. `Box` มีได้หลาย owners  
D. `Rc` เร็วกว่า

<details>
<summary>ดูเฉลย</summary>

**B. `Rc` มีได้หลาย owners**

`Rc` = Reference Counting ใช้เมื่อต้องการ shared ownership

</details>

---

# คำถาม 14.3

`RefCell<T>` ทำอะไรได้?

A. Multi-threading  
B. Interior mutability  
C. Garbage collection  
D. Network IO

<details>
<summary>ดูเฉลย</summary>

**B. Interior mutability**

`RefCell` ยอมให้ borrow mutably ทั้งที่เป็น immutable จากภายนอก

</details>

---

# คำถาม 14.4

`Weak<T>` ใช้แก้ปัญหาอะไร?

A. Memory leak  
B. Reference cycles  
C. Deadlock  
D. Race condition

<details>
<summary>ดูเฉลย</summary>

**B. Reference cycles**

`Weak` ไม่เพิ่ม reference count ป้องกัน cycle ที่ทำให้ memory leak

</details>

---

# คำถาม 14.5

`Rc::clone()` ทำอะไร?

A. Deep copy ข้อมูล  
B. เพิ่ม reference count  
C. สร้าง Box ใหม่  
D. ย้าย ownership

<details>
<summary>ดูเฉลย</summary>

**B. เพิ่ม reference count**

```rust,ignore
let a = Rc::new(5);
let b = Rc::clone(&a); // เพิ่ม count, ไม่ copy ข้อมูล

👉 Quiz บทที่ 15

Quiz: บทที่ 15 - Concurrency

คำถาม 15.1

thread::spawn ทำอะไร?

A. หยุด thread
B. สร้าง thread ใหม่
C. รวม threads
D. Lock thread

ดูเฉลย

B. สร้าง thread ใหม่

let handle = thread::spawn(|| {
    // code ที่รันใน thread ใหม่
});
```text

</details>

---

# คำถาม 15.2

`Mutex` ใช้ทำอะไร?

A. สร้าง thread  
B. ป้องกัน data race  
C. ส่ง message  
D. Copy data

<details>
<summary>ดูเฉลย</summary>

**B. ป้องกัน data race**

Mutex = Mutual Exclusion ยอมให้เข้าถึงได้ทีละ thread

</details>

---

# คำถาม 15.3

`Arc<T>` ต่างจาก `Rc<T>` อย่างไร?

A. ไม่ต่าง  
B. `Arc` thread-safe  
C. `Rc` thread-safe  
D. `Arc` เร็วกว่า

<details>
<summary>ดูเฉลย</summary>

**B. `Arc` thread-safe**

- `Rc` = single-threaded
- `Arc` = Atomic Reference Counting (thread-safe)

</details>

---

# คำถาม 15.4

`mpsc::channel()` สร้างอะไร?

A. Thread  
B. Sender และ Receiver  
C. Mutex  
D. File

<details>
<summary>ดูเฉลย</summary>

**B. Sender และ Receiver**

```rust,ignore
let (tx, rx) = mpsc::channel();
// tx = transmitter/sender
// rx = receiver
```text

</details>

---

# คำถาม 15.5

`move` closure ใช้เมื่อไหร่?

A. เมื่อต้องการ copy  
B. เมื่อต้องการ move ownership เข้า closure  
C. เมื่อต้องการ reference  
D. เมื่อต้องการ print

<details>
<summary>ดูเฉลย</summary>

**B. เมื่อต้องการ move ownership เข้า closure**

```rust,ignore
let data = vec![1, 2, 3];
thread::spawn(move || {
    println!("{:?}", data); // data moved here
});

👉 Quiz บทที่ 16

Quiz: บทที่ 16 - Async/Await

คำถาม 16.1

async fn return อะไร?

A. ค่าตรงๆ
B. Future
C. Promise
D. Task

ดูเฉลย

B. Future

async fn fetch() -> String {
    // return impl Future<Output = String>
    String::from("data")
}

คำถาม 16.2

.await ทำอะไร?

A. หยุด program
B. รอให้ Future complete
C. สร้าง thread
D. Cancel operation

ดูเฉลย

B. รอให้ Future complete

let data = fetch().await;
```text

</details>

---

# คำถาม 16.3

Tokio คืออะไร?

A. Web framework  
B. Async runtime  
C. Database  
D. Testing tool

<details>
<summary>ดูเฉลย</summary>

**B. Async runtime**

Tokio รัน async code และจัดการ tasks

</details>

---

# คำถาม 16.4

`tokio::spawn` ใช้ทำอะไร?

A. สร้าง thread  
B. สร้าง async task  
C. หยุด task  
D. Join tasks

<details>
<summary>ดูเฉลย</summary>

**B. สร้าง async task**

```rust,ignore
tokio::spawn(async {
    // background task
});
```text

</details>

---

# คำถาม 16.5

`tokio::join!` ทำอะไร?

A. รวม strings  
B. รอหลาย futures พร้อมกัน  
C. สร้าง channel  
D. Lock mutex

<details>
<summary>ดูเฉลย</summary>

**B. รอหลาย futures พร้อมกัน**

```rust,ignore
let (a, b) = tokio::join!(task1(), task2());

👉 Quiz บทที่ 17

Quiz: บทที่ 17 - Unsafe Rust

คำถาม 17.1

unsafe block ทำอะไรได้?

A. ทำให้โค้ดเร็วขึ้น
B. ปิด borrow checker
C. Dereference raw pointers
D. ทำให้ compile ได้เสมอ

ดูเฉลย

C. Dereference raw pointers

Unsafe superpowers:

  • Dereference raw pointers
  • Call unsafe functions
  • Access mutable statics
  • Implement unsafe traits
  • Access union fields

คำถาม 17.2

*const T คืออะไร?

A. Constant value
B. Raw immutable pointer
C. Smart pointer
D. Reference

ดูเฉลย

B. Raw immutable pointer

let x = 5;
let ptr: *const i32 = &x;
```text

</details>

---

# คำถาม 17.3

Raw pointer ต่างจาก reference อย่างไร?

A. ไม่ต่าง  
B. Raw pointer ไม่ guaranteed valid  
C. Reference เร็วกว่า  
D. Raw pointer thread-safe

<details>
<summary>ดูเฉลย</summary>

**B. Raw pointer ไม่ guaranteed valid**

Raw pointers:

- อาจเป็น null
- อาจชี้ไปที่ invalid memory
- ไม่มี automatic cleanup

</details>

---

# คำถาม 17.4

`extern "C"` ใช้ทำอะไร?

A. Run C program  
B. Define C-compatible ABI  
C. Import extern crate  
D. Export to JavaScript

<details>
<summary>ดูเฉลย</summary>

**B. Define C-compatible ABI**

```rust,ignore
extern "C" {
    fn abs(input: i32) -> i32;
}

คำถาม 17.5

Safe abstractions over unsafe คืออะไร?

A. ห่อ unsafe code ด้วย safe interface
B. ลบ unsafe code
C. แปลง unsafe เป็น async
D. ใช้ macros แทน

ดูเฉลย

A. ห่อ unsafe code ด้วย safe interface

pub fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    // unsafe inside, but function is safe
    unsafe { ... }
}

👉 Quiz บทที่ 18

Quiz: บทที่ 18 - Macros

คำถาม 18.1

macro_rules! ใช้สร้าง macro แบบไหน?

A. Procedural
B. Declarative
C. Attribute
D. Derive

ดูเฉลย

B. Declarative

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

คำถาม 18.2

$x:expr หมายถึง?

A. Expression ที่ชื่อ x
B. Macro parameter ที่ match expression
C. Variable x
D. Type x

ดูเฉลย

B. Macro parameter ที่ match expression

Fragment specifiers:

  • expr = expression
  • ident = identifier
  • ty = type
  • stmt = statement

คำถาม 18.3

$(...)* หมายถึงอะไร?

A. Pointer
B. Zero or more repetitions
C. Multiplication
D. Reference

ดูเฉลย

B. Zero or more repetitions

macro_rules! vec_like {
    ($($x:expr),*) => { ... };
}

คำถาม 18.4

Procedural macros ต่างจาก declarative อย่างไร?

A. ไม่ต่าง
B. Procedural ทำงานกับ token stream
C. Declarative เร็วกว่า
D. Procedural อยู่ใน file เดียว

ดูเฉลย

B. Procedural ทำงานกับ token stream

Procedural macros:

  • รับ TokenStream
  • Return TokenStream
  • ต้องอยู่ใน crate แยก

คำถาม 18.5

#[derive(Debug)] คือ macro ประเภทไหน?

A. Declarative
B. Function-like
C. Derive macro
D. Attribute macro

ดูเฉลย

C. Derive macro

Derive macros auto-implement traits


👉 Quiz บทที่ 19

Quiz: บทที่ 19 - Web Development

คำถาม 19.1

Axum เป็น?

A. Database
B. Web framework
C. Template engine
D. Testing tool

ดูเฉลย

B. Web framework

Axum เป็น web framework จาก Tokio team


คำถาม 19.2

Json<T> extractor ใช้ทำอะไร?

A. Return HTML
B. Parse JSON request body
C. Validate form
D. Handle cookies

ดูเฉลย

B. Parse JSON request body

async fn handler(Json(payload): Json<MyStruct>) { }
```text

</details>

---

# คำถาม 19.3

`Path<T>` ทำอะไร?

A. Read file  
B. Extract URL path parameters  
C. Return path string  
D. Navigate to path

<details>
<summary>ดูเฉลย</summary>

**B. Extract URL path parameters**

```rust,ignore
async fn user(Path(id): Path<u32>) { }
// GET /users/123 -> id = 123
```text

</details>

---

# คำถาม 19.4

`Router::new().route()` ทำอะไร?

A. สร้าง file  
B. กำหนด route และ handler  
C. เริ่ม server  
D. ปิด connection

<details>
<summary>ดูเฉลย</summary>

**B. กำหนด route และ handler**

```rust,ignore
Router::new()
    .route("/", get(handler))
    .route("/users", post(create_user))
```text

</details>

---

# คำถาม 19.5

`State<T>` ใช้ทำอะไร?

A. Save to database  
B. Share application state  
C. Manage sessions  
D. Handle errors

<details>
<summary>ดูเฉลย</summary>

**B. Share application state**

```rust,ignore
async fn handler(State(db): State<DatabasePool>) { }

👉 Quiz บทที่ 20

Quiz: บทที่ 20 - Final Project

คำถาม 20.1

clap crate ใช้ทำอะไร?

A. Web server
B. Command line argument parsing
C. Database
D. Testing

ดูเฉลย

B. Command line argument parsing

#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    name: String,
}

คำถาม 20.2

serde ใช้ทำอะไร?

A. Networking
B. Serialization/Deserialization
C. Logging
D. Compression

ดูเฉลย

B. Serialization/Deserialization

#[derive(Serialize, Deserialize)]
struct Data { /* ... */ }
```text

</details>

---

# คำถาม 20.3

`anyhow` crate ใช้ทำอะไร?

A. Async runtime  
B. Easy error handling  
C. Web framework  
D. Database ORM

<details>
<summary>ดูเฉลย</summary>

**B. Easy error handling**

```rust,ignore
fn main() -> anyhow::Result<()> {
    // ใช้ ? ได้กับทุก error types
    Ok(())
}

คำถาม 20.4

Project structure ที่ดีควรมีอะไร?

A. ทุกอย่างใน main.rs
B. แยก modules ตาม responsibility
C. ไม่ใช้ modules
D. ใส่ tests ใน main.rs

ดูเฉลย

B. แยก modules ตาม responsibility

src/
├── main.rs
├── lib.rs
├── models/
├── handlers/
└── utils/

คำถาม 20.5

cargo build --release ต่างจาก cargo build อย่างไร?

A. ไม่ต่าง
B. Optimized, production-ready
C. Debug mode
D. รันเร็วกว่า

ดูเฉลย

B. Optimized, production-ready

  • cargo build = debug, fast compile
  • cargo build --release = optimized, slower compile

🎉 ยินดีด้วย! คุณทำ Quiz ครบทุกบทแล้ว!

👉 กลับหน้าแรก