結構體(Struct)#

結構體(Struct) 是將多個相關的值組合成一個自訂型別的機制,類似其他語言中的「物件」或「記錄」。


1. 定義結構體#

使用 struct 關鍵字定義,每個欄位(field)都要標注型別:

1
2
3
4
5
6
struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

2. 建立實例#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

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

    println!("用戶:{}", user.username);
    println!("信箱:{}", user.email);
}

若要修改欄位,實例本身必須是 mut

1
2
let mut user = User { /* ... */ };
user.age = 31;

3. 欄位初始化簡寫#

當變數名稱與欄位名稱相同時,可以省略重複:

1
2
3
4
5
6
7
8
fn new_user(username: String, email: String) -> User {
    User {
        username, // 等同於 username: username
        email,
        age: 0,
        active: true,
    }
}

4. 結構體更新語法#

基於現有實例建立新實例時,可以用 .. 繼承未明確設定的欄位:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

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

    let user2 = User {
        email: String::from("bob@example.com"),
        ..user1 // 其餘欄位從 user1 取得
    };

    println!("user2 信箱:{}", user2.email);
    println!("user2 age:{}", user2.age);
}

注意:..user1 會移動 user1 中 String 型別的欄位所有權。


5. 元組結構體(Tuple Struct)#

元組結構體有結構體名稱但欄位沒有名稱,透過索引存取:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct Point(f64, f64);
struct Color(u8, u8, u8);

fn main() {
    let origin = Point(0.0, 0.0);
    let red = Color(255, 0, 0);

    println!("x = {}, y = {}", origin.0, origin.1);
    println!("R = {}", red.0);
}

6. 方法(Methods)#

使用 impl 區塊為結構體定義方法。第一個參數決定如何存取 self

形式意義使用時機
&self不可變借用只讀取資料,最常見
&mut self可變借用需要修改欄位
self取得所有權消耗實例,呼叫後該變數失效
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    fn area(&self) -> f64 {             // 不可變借用
        self.width * self.height
    }

    fn scale(&mut self, factor: f64) {  // 可變借用
        self.width *= factor;
        self.height *= factor;
    }

    fn into_square(self) -> Rectangle { // 取得所有權
        let side = (self.width + self.height) / 2.0;
        Rectangle { width: side, height: side }
    }
}

fn main() {
    let mut rect = Rectangle { width: 5.0, height: 3.0 };
    println!("面積:{}", rect.area());
    rect.scale(2.0);
    let sq = rect.into_square(); // rect 的所有權移入,之後 rect 失效
    println!("正方形邊長:{}", sq.width);
}

self(取得所有權)常見於 消耗型轉換into_xxx() 系列)和 Builder 模式 。Builder 模式中,每個設定方法取得並回傳 self,讓呼叫可以鏈式串接:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct QueryBuilder {
    table: String,
    limit: usize,
}

impl QueryBuilder {
    fn new(table: &str) -> Self {
        QueryBuilder { table: table.to_string(), limit: 100 }
    }

    fn limit(mut self, n: usize) -> Self {
        self.limit = n;
        self
    }

    fn build(self) -> String {
        format!("SELECT * FROM {} LIMIT {}", self.table, self.limit)
    }
}

fn main() {
    let query = QueryBuilder::new("users")
        .limit(50)
        .build();
    println!("{query}");
}

7. 關聯函數(Associated Functions)#

impl 中沒有 self 參數的函數稱為 關聯函數 ,常用來實作建構子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct Rectangle {
    width: f64,
    height: f64,
}

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

    fn square(size: f64) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

fn main() {
    let rect = Rectangle::new(4.0, 6.0);
    let sq = Rectangle::square(3.0);
    println!("rect: {}x{}", rect.width, rect.height);
    println!("sq: {}x{}", sq.width, sq.height);
}

關聯函數用 :: 呼叫,而非 .


8. Debug 輸出#

為結構體加上 #[derive(Debug)] 屬性,就能用 {:?}{:#?} 列印:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 1.5, y: 2.5 };
    println!("{:?}", p);  // Point { x: 1.5, y: 2.5 }
    println!("{:#?}", p); // 格式化輸出
}

Reference#

https://doc.rust-lang.org/book/ch05-01-defining-structs.html