結構體
struct,或者 structure,是一個自定義數據類型,允許你命名和包裝多個相關的值,從而形成一個有意義的組合。如果你熟悉一門面向對象語言,struct 就像對象中的數據屬性。結構體和我們在第三章討論過的元組類似。和元組一樣,結構體的每一部分可以是不同類型。但不同於元組,結構體需要命名各部分數據以便能清楚的表明其值的意義。由於有了這名字,結構體比元組更靈活:不需要依賴順序來指定或訪問實例中的值。也就是說元組和結構體類似,但是結構體有具體的代號
結構體的聲明和實例化
#[derive(Debug)] // 必須帶有這個 // 聲明結構體 struct User { username: String, email: String, active: bool, } fn main() { // 實例化 let mut user1 = User { // 使用mut可以修改里面的屬性 email: String::from("someone@qq.com"), username: String::from("wang"), active: true, }; user1.email = String::from("666@qq.com"); println!("{:?}", user1); }
結構體的實例構造函數
為了方便,我們可以寫一個函數專門用來返回新實例化的對象,以及字段名和變量名一致可以簡寫,這里我們比如說是builder_user可以如下:
#[derive(Debug)] // 必須帶有這個 // 聲明結構體 struct User { username: String, email: String, active: bool, } fn build_user(email: String, username: String) -> User { User { // 使用mut可以修改里面的屬性 email: email, username, // 一致就簡寫 active: true, } } fn main() { let mut user1 = build_user(String::from("xxx"), String::from("erwqrq")); user1.email = String::from("666@qq.com"); println!("{:?}", user1); }
從另一個實例來更新新實例
調用另一個實例的屬性值來創建新實例,代碼如下:
fn main() { let user1 = User { email: String::from("xxxx"), username: String::from("wang"), active: false, }; let user2 = User { email: String::from("455"), username: user1.username, active: false, }; let user3 = User { email:String::from("2232"), ..user2 // 支持結構賦值 }; println!("{:?}", user3); }
使用沒有命名字段的元組結構體來創建不同的類型
struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0,0,0); let origin = Point(0,0,0); }
結構體數據所有權
User 結構體的定義中,我們使用了自身擁有所有權的 String 類型而不是 &str 字符串 slice 類型。這是一個有意而為之的選擇,因為我們想要這個結構體擁有它所有的數據,為此只要整個結構體是有效的話其數據也是有效的。生命周期確保結構體引用的數據有效性跟結構體本身保持一致。如果你嘗試在結構體中存儲一個引用而不指定生命周期將是無效的,比如這樣:
// 聲明結構體 struct User { username: &str, email: &str, // 定義的時候就報錯了,提示沒有生命周期 active: bool, }
使用結構體創建一個demo:
// 聲明結構體 fn area(width: u32, height: u32) -> u32 { width * height } fn area1(dimensions: (u32, u32)) -> u32 { dimensions.0 * dimensions.1 } fn area2(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height } // 結構體 struct Rectangle { width: u32, height: u32, } fn main() { // 最初版本 let width = 30; let height = 50; println!("The area value :{}", area(width, height)); // 元組重構 let rect1 = (30, 50); println!("The area value :{}", area1(rect1)); let rect2 = Rectangle { width: 32, height: 55, }; println!("The area value :{}", area2(&rect2)); }
輸出結構體實例
由於Rust自帶的println!不能打印出實例,所以我們需要在{}中添加#?來打印,同時需要在文件頭部添加:#[derive(Debug)],在 {} 中加入 :? 指示符告訴 println! 我們想要使用叫做 Debug 的輸出格式。 Debug 是一個 trait,它允許我們以一種對開發者有幫助的方式打印結構體,以便當我們調試代碼時能看到它的值。
結構體方法實現
例子中,使用 &self 來替代 rectangle: &Rectangle ,因為該方法位於 implRectangle 上下文中所以 Rust 知道 self 的類型是 Rectangle 。注意仍然需要在 self 前面加上 & ,就像 &Rectangle 一樣。方法可以選擇獲取 self 的所有權,或者像我們這里一樣不可變地借用 self ,或者可變地借用 self ,就跟其他參數一樣。這里選擇 &self 的理由跟在函數版本中使用 &Rectangle 是相同的:我們並不想獲取所有權,只希望能夠讀取結構體中的數據,而不是寫入。如果想要在方法中改變調用方法的實例,需要將第一個參數改為 &mut self 。通過僅僅使用 self 作為第一個參數來使方法獲取實例的所有權是很少見的;這種技術通常用在當方法將 self 轉換成別的實例的時候,這時我們想要防止調用者在轉換之后使用原始的實例。
實例方法的調用:
在 C/C++ 語言中,有兩個不同的運算符來調用方法: . 直接在對象上調用方法,而 -> 在一個對象的指針上調用方法,這時需要先解引用(dereference)指針。換句話說,
#[derive(Debug)] struct Point { x: f64, y: f64, } impl Point { fn distance(&self, other: &Point) -> f64 { let x_squared = f64::powi(other.x - self.x, 2); let y_squared = f64::powi(other.y - self.y, 2); f64::sqrt(x_squared + y_squared) } } fn main() { let p1 = Point { x: 0.0, y: 0.0 }; let p2 = Point { x: 5.0, y: 6.5 }; println!("{}", p1.distance(&p2)); println!("{}", (&p1).distance(&p2)); }
#[derive(Debug)] // 必須帶有這個 struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } 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)); }
關聯函數
impl 塊的另一個有用的功能是:允許在 impl 塊中定義 不 以 self 作為參數的函數。這被稱為 關聯函數(associated functions),因為它們與結構體相關聯。它們仍是函數而不是
#[derive(Debug)] // 必須帶有這個 struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } } fn main() { println!("Can rect1 area {}", Rectangle::square(30).area()); }
多個impl塊
每個結構體都允許擁有多個 impl 塊。每個方法有其自己的 impl 塊。