Rust 中的結(jié)構(gòu)體(Struct)與元組(Tuple)都可以將若干個類型不一定相同的數(shù)據(jù)捆綁在一起形成整體,但結(jié)構(gòu)體的每個成員和其本身都有一個名字,這樣訪問它成員的時候就不用記住下標(biāo)了。元組常用于非定義的多值傳遞,而結(jié)構(gòu)體用于規(guī)范常用的數(shù)據(jù)結(jié)構(gòu)。結(jié)構(gòu)體的每個成員叫做"字段"。
這是一個結(jié)構(gòu)體定義:
struct Site { domain: String, name: String, nation: String, found: u32 }
注意:如果你常用 C/C++,請記住在 Rust 里 struct 語句僅用來定義,不能聲明示例,結(jié)尾不需要 ; 符號,而且每個字段定義之后用 , 分隔。
Rust 很多地方受 JavaScript 影響,在示例化結(jié)構(gòu)體的時候用 JSON 對象的 key: value 語法來實現(xiàn)定義:
let nhooo = Site { domain: String::from("www.soo66.com"), name: String::from("nhooo"), nation: String::from("China"), found: 2013 };
如果你不了解 JSON 對象,你可以不用管它,記住格式就可以了:
結(jié)構(gòu)體類名 { 字段名 : 字段值, ... }
這樣的好處是不僅使程序更加直觀,還不需要按照定義的順序來輸入成員的值。
如果正在示例化的結(jié)構(gòu)體有字段名稱和現(xiàn)存變量名稱一樣的,可以簡化書寫:
let domain = String::from("www.soo66.com"); let name = String::from("nhooo"); let nhooo = Site { domain, // 等同于 domain : domain, name, // 等同于 name : name, nation: String::from("China"), traffic: 2013 };
有這樣一種情況:你想要新建一個結(jié)構(gòu)體的示例,其中大部分屬性需要被設(shè)置成與現(xiàn)存的一個結(jié)構(gòu)體屬性一樣,僅需更改其中的一兩個字段的值,可以使用結(jié)構(gòu)體更新語法:
let site = Site { domain: String::from("www.soo66.com"), name: String::from("nhooo"), ..nhooo };
注意:..nhooo 后面不可以有逗號。這種語法不允許一成不變的復(fù)制另一個結(jié)構(gòu)體示例,意思就是說至少重新設(shè)定一個字段的值才能引用其他示例的值。
有一種更簡單的定義和使用結(jié)構(gòu)體的方式:元組結(jié)構(gòu)體。
元組結(jié)構(gòu)體是一種形式是元組的結(jié)構(gòu)體。
與元組的區(qū)別是它有名字和固定的類型格式。它存在的意義是為了處理那些需要定義類型(經(jīng)常使用)又不想太復(fù)雜的簡單數(shù)據(jù):
struct Color(u8, u8, u8); struct Point(f64, f64); let black = Color(0, 0, 0); let origin = Point(0.0, 0.0);
"顏色"和"點坐標(biāo)"是常用的兩種數(shù)據(jù)類型,但如果示例化時寫個大括號再寫上兩個名字就為了可讀性犧牲了便捷性,Rust 不會遺留這個問題。元組結(jié)構(gòu)體對象的使用方式和元組一樣,通過 . 和下標(biāo)來進(jìn)行訪問:
fn main() { struct Color(u8, u8, u8); struct Point(f64, f64); let black = Color(0, 0, 0); let origin = Point(0.0, 0.0); println!("black = ({}, {}, {})", black.0, black.1, black.2); println!("origin = ({}, {})", origin.0, origin.1); }
運行結(jié)果:
black = (0, 0, 0) origin = (0, 0)
結(jié)構(gòu)體必須掌握字段值所有權(quán),因為結(jié)構(gòu)體失效的時候會釋放所有字段。
這就是為什么本章的案例中使用了 String 類型而不使用 &str 的原因。
但這不意味著結(jié)構(gòu)體中不定義引用型字段,這需要通過"生命周期"機(jī)制來實現(xiàn)。
但現(xiàn)在還難以說明"生命周期"概念,所以只能在后面章節(jié)說明。
調(diào)試中,完整地顯示出一個結(jié)構(gòu)體示例是非常有用的。但如果我們手動的書寫一個格式會非常的不方便。所以 Rust 提供了一個方便地輸出一整個結(jié)構(gòu)體的方法:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("rect1 is {:?}", rect1); }
如第一行所示:一定要導(dǎo)入調(diào)試庫 #[derive(Debug)] ,之后在 println 和 print 宏中就可以用 {:?} 占位符輸出一整個結(jié)構(gòu)體:
rect1 is Rectangle { width: 30, height: 50 }
如果屬性較多的話可以使用另一個占位符 {:#?} 。
輸出結(jié)果:
rect1 is Rectangle { width: 30, height: 50 }
方法(Method)和函數(shù)(Function)類似,只不過它是用來操作結(jié)構(gòu)體示例的。
如果你學(xué)習(xí)過一些面向?qū)ο蟮恼Z言,那你一定很清楚函數(shù)一般放在類定義里并在函數(shù)中用 this 表示所操作的示例。
Rust 語言不是面向?qū)ο蟮?,從它所有?quán)機(jī)制的創(chuàng)新可以看出這一點。但是面向?qū)ο蟮恼滟F思想可以在 Rust 實現(xiàn)。
結(jié)構(gòu)體方法的第一個參數(shù)必須是 &self,不需聲明類型,因為 self 不是一種風(fēng)格而是關(guān)鍵字。
計算一個矩形的面積:
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!("rect1's area is {}", rect1.area()); }
輸出結(jié)果:
rect1's area is 1500
請注意,在調(diào)用結(jié)構(gòu)體方法的時候不需要填寫 self ,這是出于對使用方便性的考慮。
一個多參數(shù)的實例:
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn wider(&self, rect: &Rectangle) -> bool { self.width > rect.width } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 40, height: 20 }; println!("{}", rect1.wider(&rect2)); }
運行結(jié)果:
false
這個程序計算 rect1 是否比 rect2 更寬。
之所以"結(jié)構(gòu)體方法"不叫"結(jié)構(gòu)體函數(shù)"是因為"函數(shù)"這個名字留給了這種函數(shù):它在 impl 塊中卻沒有 &self 參數(shù)。
這種函數(shù)不依賴示例,但是使用它需要聲明是在哪個 impl 塊中的。
一直使用的 String::from 函數(shù)就是一個"關(guān)聯(lián)函數(shù)"。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } fn main() { let rect = Rectangle::create(30, 50); println!("{:?}", rect); }
運行結(jié)果:
Rectangle { width: 30, height: 50 }
貼士:結(jié)構(gòu)體 impl 塊可以寫幾次,效果相當(dāng)于它們內(nèi)容的拼接!
結(jié)構(gòu)體可以只作為一種象征而無需任何成員:
struct UnitStruct;
我們稱這種沒有身體的結(jié)構(gòu)體為單元結(jié)構(gòu)體(Unit Struct)。