107.08.19 rust 之路 12 Struct

引頸企盼 (並沒有
這不就更新了嗎
該放起司的地方放起司,該放肉片的地方放肉片,再用左右大括號括起來,美味的 struct 完成了 (?
Photo by Pablo Merchán Montes on Unsplash
struct 跟 tuple (很久之前型別那一篇有提過) 很像
但是 tuple 要存取成員時只能靠數字去索引,而 struct 多了類似 key 的方式去索引
有寫過 C/C++ 的大概都不陌生 struct
struct 就是可以有多個欄位 (fields),每個欄位有各自的型別和名字
struct User { // C++
    string username;
    string email;
    unsigned int sign_in_count;
    bool active;
};
struct User { // Rust
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

從上方可以發現主要是寫法上需要注意哪些要 ' , ' 哪些要 ' ; '
至於 Rust 最後一行為何還需要 ' , ' 呢?
這其實在 C99 的 designated initializer 也是如此
jserv 在 你所不知道的 C 語言:技巧篇 (2017-03-20) 33m32s 時有提到
除了所說的 macro 外,另外還有方便產生 C 的程式碼等其他好處 [參考]

宣告變數

注意 key: value 是用 ' : ' 給值而不是 ' = '
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

可變性

若要成員可變,則要用 mut 關鍵字,但注意不是放在成員,而是整個變數 (user1)
let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

user1.email = String::from("anotheremail@example.com");

函數回傳值

應該還記得函數最後一行為回傳值吧,放一個 struct 的實體就會回傳這個實體
注意 email 和 username 的用法兩種皆可
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

struct tuple

struct 跟 tuple 可以合用,比 tuple 多了型別名,比 struct 少了成員名
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

輸出到螢幕

struct 也可以使用 println! 這個 macro 來輸出,不過要小改造一下
1. 必須在要輸出的 struct 上方加上 #[derive(Debug)]
2. println! macro { } 中要加上 :? 或 :#? 詳細參數請參考
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("rect1 is {:?}", rect1);
    println!("rect1 is {:#?}", rect1);
}
結果如下:
rect1 is Rectangle { width: 30, height: 50 }
rect1 is Rectangle {
    width: 30,
    height: 50
}


另外還有 fmt 函式可以用,與 Java 中覆寫 toString() 有點像
但是 Rust 沒有繼承的概念,是以實作 std::fmt::Display 這個 Trait 來達成
最簡易的寫法如下,發現可以直接透過 println! 輸出了
( Trait 在之後應該會再出現
struct Rectangle {
    width: u32,
    height: u32,
}

impl std::fmt::Display for Rectangle {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "width: {}, height: {}", self.width, self.height)
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("rect1 is {}", rect1);
}
rect1 is width: 30, height: 50

方法 (Method)

方法簡單來說就是 struct 的函式
struct 裡面記錄著結構的成員,而這些成員通常是變數,方法並不包含在裡面
不像 C++、Java 直接把方法定義在類別裡面,Rust 是使用實作的方式來達成
關鍵字是 impl
第一個參數是 self 就是實體 (instance) 方法,所謂實體方法就是可以用 ' . ' 去呼叫函數
例如定義一個回傳長方形面積的方法:
struct Rectangle {
    width: u32,
    height: u32,
}

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

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("area is {}", rect1.area());
}

self

這個第一個參數 self 很偉大
它讓方法呼叫時同時決定是要 move (self)、borrow (&self)、mut borrow (&mut self) 的方式使用實體
例如改一下上面範例
impl Rectangle {
    fn area(self) -> u32 { // 把 & 拿掉
        self.width * self.height
    }
}
這樣就會產生錯誤,因為第一次呼叫 area() 時就把 rect1 move 到方法中了
let rect1 = Rectangle { width: 30, height: 50 };
println!("area is {}", rect1.area());
println!("area is {}", rect1.area()); // error

一般 &self 最常用,若要更改內部成員的話就用 &mut self
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("area is {}", rect1.area());

    let mut rect1 = rect1; // 注意要先變成 mut 才可以被修改
    rect1.set_width(40);
    println!("area is {}", rect1.area());
}

associated functions

沒有用 self 參數的就是 associated functions
有點像是 java static 函數:類別方法
使用時就不是用 ' . ' 了,而是用 ' :: ',String 的 from 就是
通常是用來回傳一個實體
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle { // associated functions
        Rectangle { width, height }
    }
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle::new(30, 50);
    println!("area is {}", rect1.area());
}

automatic referencing and dereferencing

這是 Rust 中為什麼沒有像 C/C++ 的 ' -> ' 去使用 borrow 過的成員或方法
因為 Rust 會自動加上 &、&mut、 * 所以統一用 ' . ' 即可,超方便程式碼也比較乾淨
struct Rectangle {
    width: u32,
    height: u32,
}

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

fn main() {
    let rect1 = &&&&Rectangle::new(30, 50);
    println!("area is {}", (****rect1).area());
    println!("area is {}", rect1.area());
}

多重 impl 區塊

最後,impl 區塊並不限於只能用一個
也就是可以對同一個 struct 定義不同的 impl
struct Rectangle {
    width: u32,
    height: u32,
}

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

fn main() {
    let rect1 = Rectangle::new(30, 50);
    println!("area is {}", rect1.area());
}


參考資料:
Using Structs to Structure Related Data

沒有留言:

張貼留言

^ Top