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

Structs

Struct รวม field ที่เกี่ยวข้องกันไว้ภายใต้ประเภทชื่อเดียว แต่ละ field มีชื่อและ type ของตัวเอง

struct Point {
x: f64,
y: f64,
}
let p = Point { x: 3.0, y: 4.0 };

คุณสร้าง instance โดยระบุค่าสำหรับทุก field

เข้าถึง field ด้วย dot notation: p.x, p.y โดยค่าเริ่มต้น field จะ private ต่อ module นั้น ใส่ pub นำหน้าเพื่อเปิดให้เข้าถึงจากภายนอก

คุณไม่สามารถ println!("{}", p) struct ได้ — ไม่มี format ที่มนุษย์อ่านได้เป็นค่าเริ่มต้น เพิ่ม #[derive(Debug)] เหนือ struct เพื่อเปิดใช้ format {:?} แทน ซึ่งเป็นสิ่งที่คุณใช้บ่อยระหว่างพัฒนา

fn main() {
#[derive(Debug)]
struct Point { x: i32, y: i32 }
let p = Point { x: 1, y: 2 };
println!("{:?}", p); // Point { x: 1, y: 2 }
}

#[derive(Debug)] เป็นหนึ่งใน attribute หลายตัวที่สร้าง trait implementation ให้คุณแทนที่จะเขียน boilerplate เอง derive ที่พบบ่อยที่สุด และสิ่งที่มันให้:

Deriveให้อะไร
Debugformat {:?}
Clonemethod .clone() ที่ทำสำเนาค่าอย่างชัดเจน
Copyทำสำเนาแบบ bitwise โดยนัยตอน assign/pass แทนการ move
PartialEq== และ !=
Eqคำสัญญาว่า == เป็น reflexive อย่างสมบูรณ์ (x == x เป็นจริงเสมอ) — จำเป็นเป็น bound สำหรับ key ของ HashMap/HashSet
Hashความสามารถในการเป็น key ของ HashMap/HashSet
DefaultT::default() ค่า zero/empty ที่สร้างจาก Default ของแต่ละ field
PartialOrd, Ord<, >, .sort(), .max() — เปรียบเทียบ field ตามลำดับที่ประกาศ
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
let b = a; // Copy: a is still usable after this
println!("a={:?} b={:?}", a, b);
println!("a == b: {}", a == b);
println!("default: {:?}", Point::default());
let mut pts = vec![Point { x: 3, y: 1 }, Point { x: 1, y: 5 }, Point { x: 1, y: 2 }];
pts.sort();
println!("sorted: {:?}", pts);
}

ทุก derive ที่นี่สร้างขึ้นล้วนๆ จาก field ของ struct: PartialEq เปรียบเทียบทีละ field, Ord เปรียบเทียบ field ตามลำดับที่ประกาศ (ดังนั้น x จะถูกเทียบก่อน y) และ Default เรียก i32::default() (ซึ่งคือ 0) สำหรับแต่ละ field ไม่มีอะไรวิเศษในนี้เลย — มันคือโค้ดแบบเดียวกับที่คุณจะเขียนเองทุกประการ

Copy ไม่ใช่ derive ที่คุณจะเพิ่มให้ type ไหนก็ได้ มันมีข้อกำหนดจริง 2 ข้อ และคอมไพเลอร์ตรวจสอบทั้งคู่:

1. Type ต้อง implement Clone ด้วย — Copy ถูกกำหนดเป็น trait Copy: Clone ดังนั้นการ derive Copy เพียงอย่างเดียวโดยไม่มี Clone จะล้มเหลว:

// @expect-error E0277
#[derive(Copy)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{} {}", p.x, p.y);
}
error[E0277]: the trait bound `Point: Clone` is not satisfied
--> src/main.rs:1:10
|
1 | #[derive(Copy)]
| ^^^^ the trait `Clone` is not implemented for `Point`
|
note: required by a bound in `Copy`
--> /rustc/.../library/core/src/marker.rs:454:17
|
454 | pub trait Copy: Clone {
| ^^^^^ required by this bound in `Copy`
help: consider annotating `Point` with `#[derive(Clone)]`
|
2 + #[derive(Clone)]
3 | struct Point {
|
error: aborting due to 1 previous error

2. ทุก field ต้องเป็น Copy ด้วยตัวเอง แม้จะ derive Clone ด้วยแล้ว field ที่ไม่ใช่ Copy เพียงตัวเดียว (เช่น String ที่เป็นเจ้าของ heap allocation) ก็ปิดกั้น struct ทั้งก้อน:

// @expect-error E0204
#[derive(Clone, Copy)]
struct User {
name: String,
age: u32,
}
fn main() {
let u = User { name: String::from("Ada"), age: 30 };
println!("{}", u.name);
}
error[E0204]: the trait `Copy` cannot be implemented for this type
--> src/main.rs:2:8
|
1 | #[derive(Clone, Copy)]
| ---- in this derive macro expansion
2 | struct User {
| ^^^^
3 | name: String,
| ------------ this field does not implement `Copy`
error: aborting due to 1 previous error

นี่ไม่ใช่ข้อจำกัดตามอำเภอใจ: Copy หมายถึง “การทำสำเนาค่านี้คือการคัดลอก byte ล้วนๆ โดยไม่มีผลข้างเคียง” — แต่การคัดลอก byte ของ String จะสร้างเจ้าของสองคนของ buffer เดียวกันบน heap ซึ่งคือ double-free ที่ Copy มีไว้ป้องกันพอดี Clone ยังคงใช้ได้ (.clone() แบบชัดเจนทำการคัดลอก heap จริงๆ ที่อาจมีต้นทุนสูง) ส่วน Copy ทำไม่ได้

การ derive PartialEq เปรียบเทียบ field แบบ floating-point ด้วย == ซึ่งทำงานถูกต้องสำหรับค่าปกติ แต่มีข้อยกเว้นที่รู้จักกันดีอยู่หนึ่งข้อ: NaN ไม่เท่ากับตัวมันเอง

#[derive(Debug, PartialEq)]
struct Measurement {
value: f64,
}
fn main() {
let a = Measurement { value: 1.0 };
let b = Measurement { value: 1.0 };
println!("a == b: {}", a == b);
let m = Measurement { value: f64::NAN };
println!("m == m: {}", m == m); // false — NaN breaks reflexivity
}
a == b: true
m == m: false

การรับประกันที่พังเพียงจุดเดียวนั้น — x == x ควรเป็น true เสมอ — คือเหตุผลตรงๆ ที่ f64 implement PartialEq แต่ไม่ implement Eq และ implement PartialOrd แต่ไม่ Ord และคือเหตุผลที่ standard library ปฏิเสธไม่ให้มันเป็น key ของ HashMap หรือ HashSet เลย: ทั้งคู่ต้องการ Eq + Hash และ f64 ไม่มีทั้งสองอย่าง ดู บทเรียน collections-iterators สำหรับ E0599 ตัวจริงที่คอมไพเลอร์ให้เมื่อคุณพยายาม .insert() key แบบ f64 อยู่ดี ทางแก้ เมื่อคุณต้องการ float เป็น key จริงๆ คือห่อมันด้วย newtype ที่กำหนด total order (ส่วนใหญ่ด้วยการห้าม NaN ตอนสร้าง หรือใช้ crate ประเภท ordered-float) แทนที่จะพยายาม derive ผ่านข้อจำกัดนี้

#[derive(Default)] สร้าง “ค่า zero” จาก Default ของแต่ละ field เอง (0 สำหรับตัวเลข, "" สำหรับ String, false สำหรับ bool, None สำหรับ Option) รวมกับ ..Default::default() คุณสามารถสร้างค่าที่ส่วนใหญ่เป็น default และ override เฉพาะ field ที่คุณสนใจ — syntax .. update แบบเดียวกับที่ใช้ด้านบน เพียงแต่ใช้ Default::default() เป็นต้นทางแทน instance ที่มีอยู่:

#[derive(Debug, Default)]
struct Config {
host: String,
port: u16,
verbose: bool,
}
fn main() {
let base = Config::default();
println!("base: {:?}", base);
let custom = Config { port: 9090, ..Default::default() };
println!("custom: {:?}", custom);
}
base: Config { host: "", port: 0, verbose: false }
custom: Config { host: "", port: 9090, verbose: false }

เมื่อต้องการ instance ใหม่ที่แชร์ field ส่วนใหญ่กับ instance ที่มีอยู่ (ไม่ใช่ default ใหม่) ใช้ .. กับ instance นั้นแทน:

struct Point {
x: f64,
y: f64,
}
let p = Point { x: 3.0, y: 4.0 };
let p2 = Point { x: 10.0, ..p };

โค้ดนี้คัดลอก field ที่เหลือ (y ในกรณีนี้) มาจาก p field ที่ implement Copy จะถูกคัดลอก ส่วน field ที่เป็น Move-only จะถูก move ออกจาก ต้นฉบับ

ประเด็นสุดท้ายนี้สำคัญ: ถ้า field ใดที่นำมาจาก ..base ไม่ใช่ Copy แล้ว base จะกลายเป็น partially moved — คุณใช้ base ทั้งก้อนไม่ได้อีก (เช่น ใน debug print แบบ {:?}) แม้ว่า field ที่คุณไม่ได้เอาไปจะยังอยู่ก็ตาม

// @expect-error E0382
#[derive(Debug)]
struct Point {
x: i32,
name: String,
}
fn main() {
let p1 = Point { x: 1, name: String::from("origin") };
let p2 = Point { x: 10, ..p1 }; // p1.name is moved into p2
println!("{:?}", p1); // ERROR: p1.name was moved out
println!("{:?}", p2);
}
error[E0382]: borrow of partially moved value: `p1`
--> src/main.rs:12:22
|
10 | let p2 = Point { x: 10, ..p1 }; // p1.name is moved into p2
| --------------------- value partially moved here
11 |
12 | println!("{:?}", p1); // ERROR: p1.name was moved out
| ^^ value borrowed here after partial move
|
= note: partial move occurs because `p1.name` has type `String`, which does not implement the `Copy` trait
#[derive(Debug, Default)]
struct Config {
host: String,
port: u16,
verbose: bool,
}
fn main() {
let base = Config::default();
println!("base: {:?}", base);
let custom = Config { port: 9090, ..Default::default() };
println!("custom: {:?}", custom);
}

เมื่อ type มี field ที่เป็น optional หลายตัว builder จะช่วยให้ผู้เรียกกำหนดเฉพาะตัวที่สนใจ ทีละ method call แต่ละ method ของ builder รับ self แบบ value และคืน Self ทำให้ call เรียงต่อกันได้ (chain) — และเพราะ self ถูก consume ค่าที่สร้างไม่เสร็จจะไม่มีทางถูกใช้ซ้ำโดยไม่ตั้งใจ:

#[derive(Debug)]
struct Request {
url: String,
method: String,
retries: u32,
}
struct RequestBuilder {
url: String,
method: String,
retries: u32,
}
impl RequestBuilder {
fn new(url: &str) -> Self {
RequestBuilder { url: url.to_string(), method: "GET".to_string(), retries: 0 }
}
fn method(mut self, method: &str) -> Self {
self.method = method.to_string();
self
}
fn retries(mut self, n: u32) -> Self {
self.retries = n;
self
}
fn build(self) -> Request {
Request { url: self.url, method: self.method, retries: self.retries }
}
}
#[derive(Debug)]
struct Request {
url: String,
method: String,
retries: u32,
}
struct RequestBuilder {
url: String,
method: String,
retries: u32,
}
impl RequestBuilder {
fn new(url: &str) -> Self {
RequestBuilder { url: url.to_string(), method: "GET".to_string(), retries: 0 }
}
fn method(mut self, method: &str) -> Self {
self.method = method.to_string();
self
}
fn retries(mut self, n: u32) -> Self {
self.retries = n;
self
}
fn build(self) -> Request {
Request { url: self.url, method: self.method, retries: self.retries }
}
}
fn main() {
let req = RequestBuilder::new("https://example.com")
.method("POST")
.retries(3)
.build();
println!("{:?}", req);
}

เพิ่มพฤติกรรมให้ struct ในบล็อก impl แต่ละ method รับ receiver เป็น parameter แรก

struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}

ฟังก์ชันในบล็อก impl ที่ไม่รับ parameter self เรียกว่า associated functions เรียกใช้ด้วย syntax :: ชื่อตามแบบแผนสำหรับ constructor คือ new:

struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
}
let p = Point::new(1.0, 2.0);

Self ภายในบล็อก impl คือ alias ของประเภทที่กำลัง implement — มันหมายถึง “ไม่ว่า type นี้จะเป็นอะไรก็ตามที่บล็อก impl นี้ implement ให้” ซึ่งเป็นเหตุผลว่าทำไม body ของ new เดิมยังใช้งานได้แม้ struct จะถูกเปลี่ยนชื่อภายหลัง

บล็อก impl ยังสามารถประกาศ associated consts: ค่าคงที่ที่อยู่ภายใต้ namespace ของ type เข้าถึงด้วยวิธีเดียวกับ associated function

struct Circle {
radius: f64,
}
impl Circle {
const UNIT: Self = Circle { radius: 1.0 };
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
fn main() {
println!("unit area: {:.4}", Circle::UNIT.area());
}
unit area: 3.1416

Circle::UNIT ไม่ใช่ method call (ไม่มี ()) มันเป็นค่าคงที่ตอน compile-time ของ type Circle — มีประโยชน์สำหรับค่า default ที่มีชื่อและใช้ซ้ำได้ ซึ่งไม่ใช่สิ่งเดียวกับค่า zero สารพัดประโยชน์เดียวของ #[derive(Default)]

Tuple struct ตั้งชื่อให้กับลำดับ field ที่ไม่มีชื่อ มีประโยชน์เป็น wrapper แบบ type-safe ที่เบา:

struct Color(u8, u8, u8);
let red = Color(255, 0, 0);
println!("{}", red.0); // access by index

ต่างจาก tuple ธรรมดา Color และ Point ที่มี field type เดียวกันคือคนละ type — คอมไพเลอร์จะปฏิเสธการนำมาปนกัน

Tuple struct ที่มี field เดียวเรียกว่า newtype: type แยกต่างหากที่ห่อ type ที่มีอยู่แล้ว เพียงเพื่อให้คอมไพเลอร์แยกแยะการใช้งานสอง type เดียวกันได้

struct Meters(f64);
struct Feet(f64);

Meters และ Feet ต่างก็ “แค่ f64” ตอน runtime — wrapper ไม่มีต้นทุนเลย ถูก compile หายไปทั้งหมด — แต่คอมไพเลอร์มองว่าเป็น type ที่ไม่เกี่ยวข้องกัน ดังนั้นฟังก์ชันที่คาดหวังตัวหนึ่งจึงรับอีกตัวโดยไม่ได้ตั้งใจไม่ได้:

// @expect-error E0308
struct Meters(f64);
struct Feet(f64);
fn total_distance(a: Meters, b: Meters) -> Meters {
Meters(a.0 + b.0)
}
fn main() {
let track = Meters(100.0);
let fence = Feet(30.0);
let total = total_distance(track, fence);
println!("{}", total.0);
}
error[E0308]: mismatched types
--> src/main.rs:11:39
|
11 | let total = total_distance(track, fence);
| -------------- ^^^^^ expected `Meters`, found `Feet`
| |
| arguments to this function are incorrect
|
note: function defined here
--> src/main.rs:4:4
|
4 | fn total_distance(a: Meters, b: Meters) -> Meters {
| ^^^^^^^^^^^^^^ ---------
error: aborting due to 1 previous error

Type alias ธรรมดาอย่าง type Meters = f64; จะไม่จับสิ่งนี้ได้ — type alias เป็นแค่อีกชื่อของ type เดียวกัน ดังนั้น Feet และ Meters ทั้งคู่จะเป็น f64 ล้วนๆ และการปนกันจะ compile ผ่านแบบเงียบๆ คุณค่าทั้งหมดของ newtype คือมันเป็น type ที่แยกต่างหากจริงๆ ไม่ใช่คำพ้องความหมาย

แนวคิด newtype ขยายต่อได้: struct สามารถ parameterize ด้วย marker type ที่แทน สถานะ ที่ค่ากำลังอยู่ และบล็อก impl ที่ scope กับสถานะใดสถานะหนึ่งจะเปิดเผยเฉพาะ operation ที่ถูกต้องในสถานะนั้น คอมไพเลอร์จะปฏิเสธการเรียกผิดลำดับตอน compile-time แทนที่จะเป็นตอน runtime:

struct Locked;
struct Unlocked;
struct Door<State> {
_state: std::marker::PhantomData<State>,
}
impl Door<Locked> {
fn new() -> Self {
Door { _state: std::marker::PhantomData }
}
fn unlock(self) -> Door<Unlocked> {
println!("unlocking");
Door { _state: std::marker::PhantomData }
}
}
impl Door<Unlocked> {
fn open(&self) {
println!("opening");
}
}
struct Locked;
struct Unlocked;
struct Door<State> {
_state: std::marker::PhantomData<State>,
}
impl Door<Locked> {
fn new() -> Self {
Door { _state: std::marker::PhantomData }
}
fn unlock(self) -> Door<Unlocked> {
println!("unlocking");
Door { _state: std::marker::PhantomData }
}
}
impl Door<Unlocked> {
fn open(&self) {
println!("opening");
}
}
fn main() {
let door = Door::<Locked>::new();
let door = door.unlock();
door.open();
// door.unlock(); // would not compile: Door<Unlocked> has no unlock() method
}

Door<Locked> และ Door<Unlocked> เป็นคนละ type — PhantomData<State> ไม่มีต้นทุนไบต์ใดๆ ตอน runtime มันมีไว้เพื่อให้ type checker มีอะไรใช้ผูกกับบล็อก impl เท่านั้น การเรียก .open() บน Door<Locked> ไม่ใช่การตรวจสอบตอน runtime ที่อาจถูกลืม แต่มันคือ error[E0599]: no method named open found for struct Door<Locked> เพราะ method นั้นไม่มีอยู่จริงสำหรับ type นั้น นี่คือกลไกเดียวกัน ต่อยอดไปอีกขั้น จาก newtype pattern ด้านบน

flowchart LR
  subgraph "derive tour"
  D[Debug] --> DBG["{:?} works"]
  C1[Clone] --> CL[".clone"]
  C2["Copy (needs Clone + all-Copy fields)"] --> CP["assignment duplicates, no move"]
  PE[PartialEq] --> EQ["==, !="]
  HA[Hash + Eq] --> HM["usable as a HashMap key"]
  end
  N["newtype: struct Meters(f64)"] --> TS["type-state: struct Door&lt;State&gt;"]
จาก derive เดี่ยวสู่ type-state pattern
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn is_square(&self) -> bool {
self.width == self.height
}
}
struct Color(u8, u8, u8);
fn main() {
let rect = Rectangle::new(5.0, 3.0);
println!("Rectangle: {:?}", rect);
println!("Area: {}", rect.area());
println!("Perimeter: {}", rect.perimeter());
println!("Is square: {}", rect.is_square());
let square = Rectangle::new(4.0, 4.0);
println!("Is square: {}", square.is_square());
let red = Color(255, 0, 0);
println!("Red: ({}, {}, {})", red.0, red.1, red.2);
}

Struct คือคำสัญญาเรื่อง รูปร่าง: ทุก instance มีทุก field เสมอ และ #[derive(...)] เป็นแค่วิธีขอให้คอมไพเลอร์เขียน trait implementation แบบ mechanical ทีละ field แทนการเขียนเอง newtype (และส่วนขยายของมัน type-state) คือแนวคิดเดียวกันที่แปลงเป็น label ตอน compile-time: การแทนค่าตอน runtime ไม่เปลี่ยนเลย เปลี่ยนแค่สิ่งที่ type checker จะอนุญาตให้คุณทำกับมัน

#[derive(Copy)] เรียกใช้ฟรี แต่มีเงื่อนไขล่วงหน้าจริง (Clone บวกทุก field ต้องเป็น Copy) — ใช้มันเฉพาะกับ struct ข้อมูลง่ายๆ ขนาดเล็ก struct ที่มี String หรือ Vec ไม่สามารถมีมันได้โดยโครงสร้าง และนั่นคือคอมไพเลอร์กำลังปกป้องคุณจาก double-free ไม่ใช่ข้อจำกัดตามอำเภอใจ Builder มีต้นทุนคือต้องเขียน struct ที่สองบวก method หนึ่งตัวต่อ field ที่เป็น optional แลกกับ call site ที่อ่านเหมือนประโยคและไม่มีทางถูกสร้างในสถานะครึ่งๆ กลางๆ Newtype ไม่มีต้นทุนตอน runtime แต่เพิ่ม .0 ทุกจุดที่ต้องใช้ค่าภายใน struct แบบ type-state มีต้นทุนพิธีกรรมมากขึ้นอีกนิด (PhantomData, impl block ต่อสถานะ) แลกกับการเปลี่ยน bug ประเภท “เรียก method ผิดลำดับ” ให้เป็น compile error แทน panic ตอน runtime หรือพฤติกรรมที่ผิดแบบเงียบๆ

  • “ผมสามารถเพิ่ม #[derive(Copy)] เพื่อให้ struct ส่งผ่านได้เร็วขึ้นเสมอ” Copy ไม่มีให้ทุก struct — มันต้องการ Clone บวกทุก field ต้องเป็น Copy struct ที่มี String, Vec<T> หรือ heap-owning type ใดๆ derive Copy ไม่ได้เต็มที่ ทางแก้สำหรับ “clone แพง” ไม่ใช่ Copy แต่คือการส่งผ่าน reference
  • “type alias (type Meters = f64;) ให้ความปลอดภัยเหมือน newtype (struct Meters(f64);)” type alias เป็นแค่อีกชื่อของ type เดียวกัน — Meters และ Feet ในฐานะ alias ของ f64 ใช้แทนกันได้สนิท และคอมไพเลอร์จะปนมันอย่างมีความสุข มีแค่ newtype (struct จริงที่แยกต่างหาก) เท่านั้นที่ทำให้การปนกันเป็น compile error
  • “PartialEq บน struct ที่มี field เป็น float ทำงานเหมือน integer equality ทุกประการ” มันสืบทอดความแปลกทุกอย่างของ IEEE-754 float โดยเฉพาะ NaN != NaN ซึ่งเป็นเหตุผลตรงๆ ที่ f64 implement Eq ไม่ได้และใช้เป็น key ของ HashMap/HashSet ไม่ได้ด้วย

Struct ที่ AI เขียนมักจะใช้ #[derive(Clone, Copy)] แบบอัตโนมัติกับทุก struct เล็กๆ โดยไม่ตรวจว่าทุก field รองรับมันจริงหรือไม่

🐛 หา bug ในโค้ดนี้

#[derive(Clone, Copy, Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let u = User { name: String::from("Ada"), age: 30 };
println!("{:?}", u);
}
เฉลย

โค้ดนี้ compile ไม่ผ่านเลย: error[E0204]: the trait \Copy` cannot be implemented for this type — this field does not implement `Copy`ชี้ไปที่name: String Stringเป็นเจ้าของ heap allocation ดังนั้นการทำสำเนาแบบ bitwise (สิ่งที่Copyหมายถึง) จะสร้างเจ้าของสองคนของ buffer เดียวกัน ซึ่งคือ double-free ที่Copyมีไว้ป้องกันพอดี ทางแก้คือตัดCopyออกและเก็บแค่#[derive(Clone, Debug)]ผู้เรียกที่ต้องการสำเนาจะเรียก.clone()` อย่างชัดเจน ซึ่งเป็นการ allocate heap ที่ซื่อสัตย์และมองเห็นได้ แทนที่จะเป็นการคัดลอกแบบ bitwise ที่มองไม่เห็นซึ่งไม่มีทางปลอดภัยตั้งแต่แรก

ควรใช้ receiver type ใดสำหรับ method ที่ต้องการแค่อ่าน field ของ struct?
เรียก associated function ชื่อ new บน struct ชื่อ Rectangle อย่างไร?
อะไรที่ทำให้ tuple struct ต่างจาก tuple ธรรมดา?
โค้ดนี้ compile ไม่ผ่านด้วยอะไร?
#[derive(Clone, Copy)]
struct User {
    name: String,
    age: u32,
}
ทำไม f64 ใช้เป็น key ของ HashMap ไม่ได้ ทั้งที่ implement PartialEq?
โค้ดนี้ print อะไร?
struct Meters(f64);
struct Feet(f64);
fn total_distance(a: Meters, b: Meters) -> Meters {
    Meters(a.0 + b.0)
}
fn main() {
    let track = Meters(100.0);
    let fence = Feet(30.0);
    let total = total_distance(track, fence);
    println!("{}", total.0);
}
ในตัวอย่าง type-state Door<Locked> / Door<Unlocked> จะเกิดอะไรขึ้นถ้าเรียก .open() บน Door<Locked>?