ข้าม​ไป​ยัง​เนื้อหา

ทำไม​ต้อง Rust — คำ​สัญญา​ของ ownership กับ compiler ที่​เป็น​ครู

ถ้า​คุณ​มา​จาก​สาย C#/.NET คุณ​มี​เครื่องมือ​ครบมือ​อยู่​แล้ว — dotnet CLI, NuGet, MSBuild, GC ที่​คอย​เก็บ​ขยะ​ให้​เอง และ exception ไว้​จับ​ความ​ผิดพลาด Rust เสนอ ข้อ​ตกลง คนละ​แบบ: มัน​ขอ​ให้​คุณ​บอก compiler ให้​ชัด​ว่า “ใคร​เป็น​เจ้าของ​ข้อมูล​ก้อน​ไหน” แล้ว​แลก​กับ​ความ​ยุ่งยาก​นั้น มัน​ให้​สาม​อย่าง​ที่ C# ให้​พร้อม​กัน​ทั้ง​สาม​ไม่​ได้ — หน่วย​ความ​จำ​แบบ deterministic โดย ไม่มี GC, ไม่มี null, และ​ไม่มี data race — ทั้ง​สาม​ข้อ compiler บังคับ​ตั้งแต่​ตอน compile ไม่ใช่​ปล่อย​ให้​ไป​พัง​เอา​ตอน runtime

หัวใจ​ของ​ทั้ง​คอร์ส​คือ​มุมมอง​เดียว: compiler ของ Rust คือ​ครู ไม่ใช่​อุปสรรค ช่วง​แรก​มัน​จะ​ปฏิเสธ code ของ​คุณ​บ่อย​จน​น่า​หงุดหงิด แต่​ทุก error ที่​มัน​โยนกลับ​มา​คือ bug ที่​มัน​กัน​ไว้​ให้​ล่วงหน้า — double-free, dangling pointer, หรือ data race ที่​ใน​โลก C# คุณ​จะ​ไป​เจอ​เอา​ตอน runtime (หรือ​ไม่​เจอ​เลย​จน​ขึ้น production) เรา​จะ​ย้ำ​มุม​นี้​ซ้ำ​ทุก​บท และ​ใน​บท​นี้​เรา​แค่​วาง​กรอบ​ความคิด ติดตั้ง​เครื่องมือ แล้ว​สร้าง project แรก

📦 kaen-taskcli

code ลงมือ​ของ​คอร์ส​นี้​อยู่​ใน repo kaen-taskcli (code ตัวอย่าง​กำลัง​จัด​ทำ) — เรา​สร้าง CLI ตัว​เดียว​ชื่อ taskcli (ตัว​จัดการ​งาน to-do ที่​เก็บ​สถานะ​เป็น JSON) ไล่​ยาว 8 บท ด้วย​ภาษา​แกน + std + library ที่​นิ่ง​จริง​เพียง​ไม่​กี่​ตัว จงใจ ไม่​แตะ async ทั้ง​กอง บท​นี้​เป็น​บท​แนวคิด + การ​ตั้ง​ค่า​ครั้ง​แรก — code รัน​ได้​จริง​มี​แค่ hello-world ที่ cargo สร้าง​ให้ code feature จริง​เริ่ม​ที่​บท2 บท​นี้​ตั้ง precedent ของ honesty spine กับ 🔗 callout อ้างอิง​ต้นทาง ให้​ทุก​บท​หลัง​จาก​นี้​เดิน​ตาม

สาม​คำ​สัญญา​ที่ compiler บังคับ​ให้ — และ​ทำไม​มัน​คือ​ครู

หัวข้อ​ที่​มีชื่อ​ว่า “สาม​คำ​สัญญา​ที่ compiler บังคับ​ให้ — และ​ทำไม​มัน​คือ​ครู”

Rust สัญญา​สาม​ข้อ และ​ทุก​ข้อ​คือ สะพาน จาก​สิ่ง​ที่​คุณ​รู้​ใน C# แล้ว ข้าม​ไป ไม่ใช่​ของ​อย่าง​เดียวกันเป๊ะๆ นี่​คือ​กฎ​ของ​คอร์ส​นี้​ที่​จะ​ยึด​ตลอด: ทุก​ครั้ง​ที่​บอกว่า “เหมือน C# X” จะมี ”…ยกเว้น” ตาม​มา​เสมอ

สิ่ง​ที่​คุณ​รู้​ใน C#/.NETRust ให้​แทน…ยกเว้น (สะพาน ไม่ใช่​ของ​เดียวกัน)
GC เก็บ​ทุก object ที่​ยัง reachable แล้ว​คืน​หน่วย​ความ​จำ​แบบ​ไม่​กำหนด​เวลาเจ้าของ​เดียว; ค่า​ถูก drop แบบ deterministic เมื่อ​เจ้าของ​หมด scope (RAII)ไม่มี GC pause ไม่มี finalizer สุ่ม​จังหวะ — แต่​คุณ​ต้อง ตัดสิน​ใจ​เอง​ว่า​ใคร​เป็น​เจ้าของ​อะไร คล้าย using/IDisposable แต่​อัตโนมัติ​และ​ครอบ​ทุก​อย่าง
null + NullReferenceException โผล่​ตอน runtimeOption<T> = Some(T) หรือ None”ไม่มี​ค่า” แทน​ไม่​ได้​เลย​ถ้า​ชนิด​ไม่ใช่ Option<T>; compiler บังคับ ให้​จัดการ​กรณี None ก่อน​ใช้​ค่า ไม่ใช่​แค่ warning แบบ nullable-reference-type
lock(obj) { … } เป็น​แค่​ธรรมเนียม ข้าม​ได้ ไม่มี​ใคร​กัน​การ share ข้าม threadกฎ ownership/borrowing ทำให้ data race กลาย​เป็น compile errorเรา​จะ​เห็น​เต็มๆ ในบท8 — ตรง​นี้​แค่​รู้​ว่า Rust พิสูจน์ ความ​ปลอดภัย​ข้าม thread ตั้งแต่ compile ไม่ใช่​หวัง​ว่า​คน​เขียน​จะ​ใส่ lock ครบ

สาม​แถว​นี้​คือ “ครู” ใน​ภาค​ปฏิบัติ — คุณ​จ่าย​ค่า​เล่าเรียน​เป็น compile error ช่วง​แรก แล้ว​ได้​คืน​เป็น bug ทั้ง​ชนชั้น​ที่​หาย​ไป​จาก runtime ตลอด​กาล ใน​บท​นี้​เรา​จะ​ยัง​ไม่​ลง​ลึก​กลไก (ownership เต็มๆ อยู่​บท2) แค่​รับปาก​ไว้​ว่า​นี่​คือ​สิ่ง​ที่​เรา​กำลัง​จะ​แลก

std เท่านั้น — และ​เรา​พูด​ออก​มาตรงๆ

เรา​สร้าง taskcli ด้วย​ภาษา​แกน + std + library ที่​นิ่ง​จริง​ไม่​กี่​ตัว (เช่น clap, serde, thiserror ใน​บท​หลัง) และ จงใจ​ข้าม async ecosystem ทั้ง​กอง (tokio, async-std, axum, reqwest) — concurrency ของ​คอร์ส​นี้​คือ std::thread + std::sync ล้วนๆ

เหตุผล: async เกิด​มา​เพื่อ multiplex งาน I/O นับ​หมื่น​งาน​บน thread ไม่​กี่​ตัว แต่ CLI ที่​ทำงาน​ปริมาณ​จำกัด​ต้องการ OS thread ตรงๆ มากกว่า — reflex “await มัน​สิ” ของ C# map มา​ที่ std::thread ไม่ใช่ Rust async ซึ่ง​เป็น​โลก runtime แยก​ต่างหาก​ที่​ต้อง opt-in นี่​ตรง​กับ final project ของ​หนังสือ The Rust Programming Language เอง​ที่​เป็น web server แบบ multithread ไม่ใช่ async — เป็น design ของ​คอร์ส ไม่ใช่​ช่อง​โหว่

ติดตั้ง Rust ครั้ง​เดียว​ผ่าน rustup (จาก rustup.rs) แล้ว​คุณ​จะ​ได้​เครื่องมือ​ที่​แบ่ง​งาน​กัน​ชัดเจน:

  • rustup — ตัว​จัดการ toolchain และ component: เพิ่ม/อัปเดต/สลับ​รุ่น stable, ติดตั้ง clippy กับ rustfmt, เพิ่ม target สำหรับ cross-compile
  • cargocargoเครื่องมือ build + package manager ของ Rust (รวม dotnet CLI + NuGet + MSBuild ไว้​ใน​ตัว​เดียว)build system + package manager ที่​คุณ​จะ​ใช้​แทบ​ทุก​คำ​สั่ง​ใน​แต่ละ​วัน
  • rustc — ตัว compiler จริง​ที่ cargo เรียก​ให้​อัตโนมัติ คุณ​แทบ​ไม่​เคย​เรียก​มัน​เอง​ด้วย​มือ
  • (เสริม​แต่​ควร​มี) rust-analyzer — LSP สำหรับ editor ให้ autocomplete และ inline error ตอน​พิมพ์ ต่าง​จาก compile แล้ว​ค่อย​ดู

ถ้า​เทียบ​กับ​ฝั่ง .NET จะ​จับ​ภาพ​ได้​เร็ว​ขึ้น — แต่​ทุก​แถว​เป็น​สะพาน ไม่ใช่​ของ​เท่า​กัน​เป๊ะ:

Rustเทียบ​กับ​ฝั่ง .NETจุด​ที่​ต่าง
rustupการ​จัดการ version .NET SDKRust มี stable channel เดียว​ที่​เร็ว และ ไม่มี LTS — คุณ​อยู่​กับ​รุ่น​ล่าสุด​เสมอ ต่าง​จาก​การ pin .NET 8 LTS ไว้​หลาย​ปี
cargodotnet CLI + NuGet + MSBuild หลอม​รวม​เป็น​ตัว​เดียวคำ​สั่ง​เดียว​คุม​ทั้ง build, test, dependency, publish; file Cargo.toml เล็ก​กว่า .csproj มาก
rustccsc/Roslyn ที่​คุณ​ไม่​เคย​เรียก​ด้วย​มือcargo เรียก​ให้​เอง​เบื้องหลัง
cargo newdotnet new consoleได้​โครง project พร้อม git และ hello-world ใน​คำ​สั่ง​เดียว
crates.ionuget.orgทะเบียน​กลาง​ของ crate (ชื่อ​เรียก library ของ Rust)

มา​ลอง​จริง สั่ง cargo new hello_cargo แล้ว cargo จะ​กาง​โครง project ให้:

$ cargo new hello_cargo
Creating binary (application) `hello_cargo` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

สังเกต​บรรทัด note: — cargo ชี้​ไป​ที่​หน้า​คู่มือ manifest ให้ บน​เทอร์มินัล (ใน rustc 1.97.1 คำ​อธิบาย​นี้​พิมพ์​เป็น note บนจอ ไม่​ได้​ฝัง​เป็น comment ลงใน file อีก​ต่อ​ไป) file Cargo.toml ที่​ได้​จริง — นี่​คือ manifest หัวใจ​ของ​ทุก project cargo — หน้าตา​แบบนี้เป๊ะ:

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

[dependencies] ว่างเปล่า — บท​นี้​ยัง ไม่มี dependency ใดๆ ทั้งสิ้น taskcli จะ​เริ่ม​เพิ่ม crate จริง​ใน​บท​หลัง และ file src/main.rs:

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

สังเกต​เครื่องหมาย ! ท้าย println! — นั่น​บอกว่า​มัน​เป็น macro ไม่ใช่ method หรือ function ธรรมดา macro ถูก​กาง​ออก (expand) เป็น code ตอน compile และ​รับ argument ได้​ยืดหยุ่น​กว่า function — เรา​จะ​เจอ println!, vec!, format! บ่อย​ตลอด​คอร์ส จำ ! ไว้​ว่า “นี่​คือ​การ​เรียก macro” สั่ง cargo run เพื่อ compile แล้ว​รัน​ใน​คำ​สั่ง​เดียว บรรทัด​สุดท้าย​ที่​ได้​คือ Hello, world!

พอ project เริ่ม​โต คุณ​จะ​วน​ใช้​คำ​สั่ง​ชุด​นี้​ซ้ำๆ จน​ติดมือ:

  • cargo check — type-check + borrow-check ทั้ง project โดย​ไม่​สร้าง binary เป็น feedback loop ที่​เร็ว​ที่สุด และ ไม่มี​คู่​เทียบตรงๆ ใน C# บน CLI (Roslyn มี analyzer ใน IDE แต่ cargo check เป็น​ขั้น CLI ที่​ตั้งใจ​ออกแบบ​มา​ให้​เร็ว)
  • cargo build / cargo build --release — สร้าง binary (--release เปิด optimization เต็ม​ที่ แต่ compile ช้า​กว่า)
  • cargo run — build แล้ว​รัน​ใน​ที​เดียว
  • cargo test — รัน test ทั้ง unit และ integration
  • cargo fmt — จัด​รูปแบบ code ตาม​มาตรฐาน​กลาง (เลิก​เถียง​เรื่อง​สไตล์)
  • cargo clippy — linter ที่​จับ anti-pattern และ​เสนอ​สำนวน​ที่​เป็น Rust มาก​ขึ้น

ตาม​คู่มือ The Cargo Book คำ​สั่ง cargo check “type-checks the current package without producing a binary” — นั่น​คือ​เหตุผล​ที่​มัน​เร็ว​พอ​จะ​รัน​หลัง​พิมพ์​ทุก​ไม่​กี่​วินาที ต่าง​จาก cargo build ที่​ต้อง codegen + link จน​ได้ file รัน​จริง

flowchart TB
  RU["rustup<br/>จัดการ toolchain + component"]
  RU -->|"ติดตั้งให้"| RC["rustc<br/>ตัว compiler จริง"]
  RU -->|"ติดตั้งให้"| CG["cargo<br/>build + package manager"]
  RU -->|"ติดตั้งให้"| CF["clippy + rustfmt<br/>linter + ตัวจัดรูปแบบ"]
  CG -->|"เรียกใช้ให้อัตโนมัติ"| RC

  subgraph LOOP["วงจร cargo ที่ใช้ทุกวัน"]
    direction LR
    CK["cargo check<br/>type-check ไม่สร้าง binary"] --> BD["cargo build"] --> RN["cargo run"] --> TS["cargo test"]
  end
  CG --> LOOP

  classDef tool fill:#fde68a,stroke:#92400e,color:#451a03;
  classDef loop fill:#fed7aa,stroke:#7c2d12,color:#431407;
  class RU,RC,CG,CF tool;
  class CK,BD,RN,TS loop;

คำ​บรรยาย​ภาพ: เครื่องมือ​ของ Rust และ​วงจร cargo ที่​ใช้​ทุก​วัน — rustup ติดตั้ง rustc, cargo, clippy และ rustfmt ให้; ใน​ชีวิต​ประจำ​วัน​คุณ​คุย​กับ cargo เป็น​หลัก (ซึ่ง​เรียก rustc ให้​เบื้องหลัง) แล้ว​วน​อยู่​ใน loop cargo checkbuildruntest โดย​เริ่ม​จาก check ที่​เร็ว​ที่สุด​เพราะ​ไม่​ต้อง​สร้าง binary

มือใหม่​มัก​สับสน​สอง​เลข​นี้ ทั้ง​ที่​มัน​คนละ​เรื่อง​กัน​สิ้นเชิง:

  • editioneditionรุ่น​ภาษา​แบบ opt-in (2015/2018/2021/2024) เลือก​ต่อ crate; ทุก edition ทำงาน​ร่วม​กัน​ได้ (เช่น edition = "2024" ใน Cargo.toml) คือ “ยุค” ของ​ภาษา​แบบ opt-in ออกราว​ทุก ~3 ปี (2015 → 2018 → 2021 → 2024) เลือก​ได้ ต่อ crate
  • version compiler (เช่น rustc 1.97.1) คือ​ตัว compiler เอง ออก stable ใหม่​ทุก ~6 สัปดาห์​แบบ​นาฬิกา​ปลุก

หนังสือ The Rust Programming Language (Appendix G) สรุป​คาบ​การ​ออก​ไว้​ว่า “A new stable Rust is released every six weeks, like clockwork” และ​รองรับ​เฉพาะ stable ล่าสุด​เท่านั้น ส่วน edition เป็น​กลไก​คนละ​แกน — ตาม Rust Edition Guide edition คือ​วิธี “ปล่อย​การ​เปลี่ยนแปลง​ที่ break ความ​เข้า​กัน​ได้​เล็กๆ แบบ opt-in โดย ไม่​แตก​ระบบ​นิเวศ; crate ต่าง edition ทำงาน​ร่วม​กัน​ได้​อย่าง​ราบรื่น” นั่น​แปล​ว่า code edition 2015 เก่า​ยัง compile ด้วย rustc รุ่น​ล่าสุด​ได้ และ​คุณ​อัปเกรด​ที​ละ crate ได้​โดย​ไม่​ต้อง​รอ​ทั้ง project

edition 2024 นั้น ถูก stabilize ใน Rust 1.85.0 (2025-02-20) และ​เป็น edition ปริยาย​ของ cargo new (ตาม block ประกาศ Rust 1.85.0) — นี่​คือ​เหตุผล​ที่ Cargo.toml ของ​เรา​ขึ้น edition = "2024" เอง ตรง​นี้​คือ​ความ​ต่าง​จาก .NET ที่​มี LTS ให้ pin: Rust ไม่มี LTS แต่ edition คือ​สิ่ง​ที่​ทำให้การ “อยู่​กับ stable ล่าสุด​เสมอ” ไม่​เจ็บ

version จะ​ขยับ​ใต้เท้า​คุณ — และ​วิธี​เช็ก

ณ เวลา​ที่​เขียน​บท​นี้ (as-of 2026-07-16) stable ล่าสุด​คือ Rust 1.97.1 — แต่​เพราะ​ออก​ใหม่​ทุก ~6 สัปดาห์ เลข​นี้​แทบ​จะ​ขยับ​ไป​แล้ว​ตอน​คุณ​อ่าน ทั้ง​คอร์ส​นี้ pin version พร้อม​วัน​ที่​กำกับ​เสมอ และ​คุณ​เช็ก​ของ​จริง​เอง​ได้​ตลอด​ด้วย rustc --version กับ​หน้า blog.rust-lang.org/releases — ตัวเลข​ที่ ไม่ ขยับ​คือ edition = "2024" เพราะ edition ผูก​กับ crate ไม่ใช่​กับ​รุ่น compiler

บท​นี้​วาง​กรอบ​ว่า​ทำไม​วิศวกร C#/.NET ถึง​คุ้ม​ที่​จะ​ข้าม​มา Rust: หน่วย​ความ​จำ deterministic ไม่มี GC, ไม่มี null (Option<T>), ไม่มี data race — โดย compiler บังคับ​ตั้งแต่​ตอน compile และ​เรา​เลือก​มอง compiler เป็น ครู ไม่ใช่​อุปสรรค; เรา​รับปาก​เรื่อง std-only ที่​จงใจ​ข้าม async พร้อม​เหตุผล; รู้จัก​เครื่องมือ​สาม​ชิ้น rustup/cargo/rustc (+ rust-analyzer) และ​การ map มัน​กับ .NET toolchain; รัน cargo new hello_cargo เดิน​ดู Cargo.toml กับ src/main.rs (จำ ! = macro); ผูก​วงจร cargo checkbuildruntest ไว้​ใน​มือ; และ​แยก edition (ยุค​ภาษา, ~3 ปี, opt-in, ทำงาน​ข้าม edition ได้) ออก​จาก version compiler (1.97, ทุก 6 สัปดาห์) บท​นี้​ยัง​ไม่มี code feature — มี​แค่ hello-world ที่ cargo สร้าง​ให้

บท​หน้า​เรา​ลง​ของ​จริง: ownership, การ​ยืม (borrowing) และ​การ​ย้าย (move) — model หลัก​ที่​ทุก​อย่าง​ใน​คอร์ส​นี้​ตั้ง​อยู่​บน​มัน เรา​จะ​เห็น compiler-ครู​โยน error ตัว​แรก (E0382) แล้ว​เข้าใจ​ว่า​มัน​กัน double-free ให้​เรา​ได้​อย่างไร อย่า​รีบ​ผ่าน​บท​นั้น — ทั้ง​คอร์ส​พัก​อยู่​บน​ไอเดีย​เดียว​นี้


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้

บท​นี้​อิง​ต้นทาง​ที่​ลง​วัน​ที่​กำกับ อ่าน​ต่อ​ได้​โดยตรง:

เช็กความเข้าใจ — บทที่ 1

ข้อ 1 / 3

`cargo check` ทำอะไร และทำไมมันถึงเป็นคำสั่งที่ใช้บ่อยในวงจรชั้นใน?