struct 是自訂資料類別,可包裝多個相關的 values。如果是物件導向程式語言, struct 就像是物件中的 attributes,以下會討論 tuple 跟 struct 的差異,說明 struct 的使用方法,並討論如何定義 method 與相關函數,來限制 structs 資料的行為。Structs 與 Enum 都是建立新類別的方法,並充分運用了 Rust 的編譯時期資料型別檢查的功能。
Defining and Instantiating Structs
struct 跟 tuple 類似,每一個部分可以是不同的資料型別,但不同於 tuple,struct 需要為每一個部分的資料命名,表達其值的意義。因為有了名字,struct 比 tuple 靈活,不需要靠順序來存取裡面的值。
struct 的每一個部分的資料稱為 field
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
定義 struct 後,透過 key:value 名稱來存取裡面的值,例如下面是修改 user1 的 email
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");
Using the Filed Init Shorthand when Variables and Fields Have the same value
這是產生 user 的 function
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
當 field 的名稱與 value 都相同時,email 以及 username 都是一樣的,可使用 field init shorthand 改寫這個 function
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
Creating Instances From Other Instances With Struct Update Syntax
透過 struct update syntax 這種語法更新 struct 裡面的 field
這是用 user1 的兩個欄位,產生新的 user2
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
可改用這種語法
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
Using Tuple Structs without Named Fields to Create Different Types
tuple 跟 struct 類似,但沒有欄位名稱。如果想要為 tuple 命名,可使用 tuple structs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
雖然都是一樣的結構,但 black 跟 origin 是不同的類別
Unit-Like Structs Without Any Fields
unit-line struct 類似於 ()
,就是 unit 類別,裡面沒有任何 fields。可用在想讓某個類別實現 trait,但不需要再類別中儲存資料的狀況。
Ownership of Struct Data
如果在 Struct 中使用了 String,而不是 &str 的 slice,這就是要讓這個 struct 自己擁有這個欄位的資料,只要 struct 有效,資料就有效。
也可以讓 struct 儲存被其他類別擁有的資料的 reference,但就需要用到 lifetimes 的概念。
lifetime 可確保 struct 引用的資料有效性跟 struct 本身保持一致。
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
編譯錯誤
error[E0106]: missing lifetime specifier
--> src/main.rs:2:15
|
2 | username: &str,
| ^ expected lifetime parameter
error[E0106]: missing lifetime specifier
--> src/main.rs:3:12
|
3 | email: &str,
| ^ expected lifetime parameter
chap 10 會討論 lifetime,解決這邊的錯誤。
一個使用 struct 的範例
撰寫一個計算長方形面積的程式。
先用基本的變數方式撰寫
fn main() {
let width1 = 30;
let height1 = 50;
println!(
"The area of the rectangle is {} square pixels.",
area(width1, height1)
);
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
fn area 有兩個參數,但沒有表現出相關的特性,可以改用 tuple 或 struct
這是 tuple 的版本,但沒有明確說明哪一個參數是長,哪一個是寬
fn main() {
let rect1 = (30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}
fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
用 struct 改寫
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
利用 derived traits 增加功能
以下程式,在列印 Rectangle 時會發生編譯 error
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {}", rect1);
}
編譯錯誤
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
--> src/main.rs:9:29
|
9 | println!("rect1 is {}", rect1);
| ^^^^^ `Rectangle` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
println! 巨集裡面的 {}
,預設是使用 Display
格式,對於 struct 並沒有 Display。錯誤訊息說明,要加上 #[derive(Debug)]
,並改用 {:?}
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {:?}", rect1);
}
#[derive(Debug)]
是利用註解來 derive Debug trait,而 {:?}
是列印的格式,結果為
rect1 is Rectangle { width: 30, height: 50 }
如果改用 {:#?}
,輸出資料就會變成
rect1 is Rectangle {
width: 30,
height: 50
}
rust 提供了很多透過 dervie 註解使用的 trait,可謂自訂類別增加一些常用的功能。
Method Syntax
method 跟函數一樣,都適用 fn 宣告,可以有參數及回傳值,但 method 跟 function 不同,在 struct (或是 enum, trait) 的 context 中被定義,第一個參數永遠是 self
,就是呼叫該 method 的 struct instance。
Defining Methods
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// self 是 Rectangle,必須加上 &
// method 可以取得 self 的 ownership,也可以用 &self 不可變借用,或是 &mut self 可變借用
// 如果想要在 method 裡面改變資料,就要改用 &mut self
// 很少會直接寫成 self
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
where's the -> operator ?
C/C++ 有兩種方式呼叫 method, .
直接在 object 上呼叫 method 或是 ->
在 object 的 pointer 呼叫 method。這邊要先了解 dereference pointer。假設 object 是 pointer,那麼 object->something()
就跟 (*object).somethong()
一樣。
當 object.something()
呼叫 method 時,rust 會自動為 object 加上 &
, &mut
或 *
,以便讓 object 跟 method signature 匹配。
這兩種寫法是一樣的
p1.distance(&p2);
(&p1).distance(&p2);
自動引用是因為 method 有明確的接收者 self,在提供了接收者與 method 名稱條件下,rust 可計算出要使用 &self
, &mut self
或是 self
Methods with More Parameters
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
// 取得另一個矩形的 immutable reference,只讀取資料,不需要寫入
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!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
Associated Functions
impl 區塊的另一個功能:允許在 impl 中定義,不使用 self 為參數的函數,這種函數稱為 assoicated function。因他們與 struct 結構相關,是函數而不是 method,因為他們不作用於一個 struct 的 instance。
產生 struct 新 instance 的 factory function,常常是用 associated function 實作。
接受一個參數,產生正方形 Rectangle
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
用 ::
呼叫 associated function,例如 let sq = Rectangle::square(3);
Multiple impl blocks
每個 struct 都允許有多個 impl blocks
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
沒有留言:
張貼留言