corutine rust_Rust学习笔记#5:函数和trait
函數(shù)
基本語法
Rust的函數(shù)使用fn關(guān)鍵字開頭,函數(shù)可以有一系列的輸入?yún)?shù),還有一個返回類型。函數(shù)返回可以使用return語句,可以使用表達式。下面是一個標(biāo)準(zhǔn)函數(shù)的示例,add函數(shù)接受兩個i32的參數(shù),然后計算它們的和并返回:
fn add(a: i32, b: i32) -> i32 {
a + b
}
println!("{}", add(1, 2)); // 輸出:3
函數(shù)返回值如果不顯示標(biāo)明,默認是()。函數(shù)返回值類型也可以是never類型!,這一類函數(shù)叫做發(fā)散函數(shù),代表這個函數(shù)不能夠正常返回,例如:
fn diverges() -> ! {
panic!("This function never return!")
}
Rust編寫的可執(zhí)行程序的入口是fn main() -> ()函數(shù)。一般情況下,一個進程開始執(zhí)行的時候可以接受一系列的參數(shù),退出的時候也可以返回一個錯誤碼,所以很多編程語言會為main函數(shù)設(shè)計參數(shù)和返回值類型,例如C語言中的int main(int argc, char **argv)。但是,Rust的main函數(shù)無參數(shù)也無返回值,其傳遞參數(shù)和返回狀態(tài)碼都通過單獨的API來完成,示例如下:
fn main() {
for arg in std::env::args() {
println!("Arg: {}", arg);
}
}
可以通過std::env::args()函數(shù)獲取參數(shù),通過exit()函數(shù)的參數(shù)傳遞錯誤碼,通過std::env::var()讀取環(huán)境變量。
函數(shù)遞歸與TCO
函數(shù)遞歸是我們常用的一種思維方式,Rust也支持函數(shù)遞歸調(diào)用。下面用經(jīng)典的Fibonacci數(shù)列來舉例:
fn fib(index: u32) -> u64 {
match index {
1 | 2 => 1,
_ => fib(index - 1) + fib(index - 2),
}
}
println!("{}", fib(25)); // 輸出:75025
談起遞歸,我們都會想到尾遞歸優(yōu)化的概念(TCO,Tail Call Optimization)。TCO可以把尾遞歸在編譯階段轉(zhuǎn)換為迭代循環(huán)從而降低時間和空間開銷,但Rust并不支持TCO,某個RFC的作者給出了以下理由:
可移植性問題,LLVM當(dāng)時在某些指定架構(gòu)上特別是MIPS和WebAssembly,不支持正確尾調(diào)用。
LLVM中正確尾調(diào)用實際上可能會由于它們當(dāng)時的實現(xiàn)方式而造成性能損失。
TCO讓調(diào)試變得更加困難,因為它重寫了棧上的值。
方法
在一些編程語言中,函數(shù)和方法往往是對同一種東西的兩種稱呼,但在Rust中,它們是有明確區(qū)分的。方法和函數(shù)的語法完全相同:它們使用 fn 關(guān)鍵字和名稱聲明,可以擁有參數(shù)和返回值,同時包含在某處調(diào)用該方法時會執(zhí)行的代碼。但是,只有在結(jié)構(gòu)體上下文中被定義的函數(shù)才能稱為方法。
方法分為成員方法和靜態(tài)方法,它們的區(qū)別在于第一個參數(shù)是否為self。Rust中的Self和self都是關(guān)鍵字,其中Self是類型名,self是變量名,self代表調(diào)用該方法的結(jié)構(gòu)體實例,靜態(tài)方法屬于結(jié)構(gòu)體類型所有,所以不需要self。常見的Self和self的組合有: self: Self(獲得所有權(quán))、self: &self(僅僅讀取)、self: &mut self(做出修改),因為它們的使用頻率很高,Rust也提供了相應(yīng)的語法糖來簡寫:self、&self、&mut self。
成員方法
可以使用impl為結(jié)構(gòu)體實現(xiàn)成員方法,如下面的代碼所示。將函數(shù)移到impl塊中,并將第一個參數(shù)改成self,就把函數(shù)變成了成員方法。和其他編程語言類似,調(diào)用成員方法只需在結(jié)構(gòu)體示例后加.即可。每個結(jié)構(gòu)體可以擁有多個impl塊。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect.area()
);
}
靜態(tài)方法
在impl塊中定義的不以self作為參數(shù)的方法就是靜態(tài)方法,見下面的代碼。可以使用結(jié)構(gòu)體名和::運算符來調(diào)用靜態(tài)方法,例如let sq = Rectangle::square(3);來獲得一個大小為3的正方形。
// 接上面
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
trait
基本語法
trait的功能類似于Java中的接口,trait可以翻譯為“特性”,它可以為多種類型抽象出共同擁有的一些功能。一個類型的行為由其可供調(diào)用的方法構(gòu)成。如果可以對不同類型調(diào)用相同的方法的話,這些類型就可以共享相同的行為了。trait 定義將方法簽名組合起來,目的是定義一個實現(xiàn)某些目的所必需的行為的集合。這些方法既可以是成員方法,也可以是靜態(tài)方法。trait可以用關(guān)鍵字trait來聲明,例如:
trait Shape {
fn area(&self) -> u32;
fn hello();
}
上面的代碼聲明了一個名為Shape的trait,它有一個名為area的方法,即,若某個類型擁有Shape這個特性,那么它一定可以求面積。為某個類型實現(xiàn)trait使用impl...for關(guān)鍵字,例如:
struct Rectangle {
width: u32,
height: u32,
}
trait Shape {
fn area(&self) -> u32;
fn hello();
}
impl Shape for Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn hello() {
println!("Just say hello!")
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect.area()
);
Rectangle::hello();
}
trait中的方法也可以有默認實現(xiàn),那么在針對具體類型實現(xiàn)的時候,就可以不用重寫。我們也可以利用trait為其他類型擴展方法,哪怕這個類型不是我們自己寫的。例如,可以為內(nèi)置類型i32添加一個方法:
trait Double {
fn double(&self) -> i32;
}
impl Double for i32 {
fn double(&self) -> i32 {
*self * 2
}
}
fn main() {
let i: i32 = 5;
println!("{}", i.double()); // 輸出:10
}
孤兒規(guī)則
使用trait為類型擴展方法也要受到一定的限制,在聲明trait和impl trait的時候,Rust規(guī)定了一個一致性規(guī)則,也稱為孤兒規(guī)則:impl塊要么與trait的聲明在同一個crate中,要么與類型的聲明在同一個crate中。也就是說,如果trait和類型都來自于外部的crate,那么便不允許為這個類型實現(xiàn)該trait。這是因為,該類型沒有實現(xiàn)該trait,這可能是該類型作者有意的設(shè)計,強行實現(xiàn)可能會導(dǎo)致bug。
trait和接口的區(qū)別
之前我們說trait和接口在功能上類似,但它們在使用中是有區(qū)別的。trait本身不是具體類型,也不是指針類型,它只是定義了針對類型的抽象的約束,不同的類型可以實現(xiàn)同一個trait,而這些類型可能具有不同的大小,因此trait在編譯階段沒有固定大小,所以,Rust中不能直接使用trait作為實例變量、參數(shù)和返回值,這一點和接口的習(xí)慣用法是不同的。例如,下面的代碼就是編譯錯誤的:
trait Shape {
fn area(&self) -> u32;
}
// error[E0277]: the size for values of type `(dyn Shape + 'static)` cannot be known at compilation time
fn use_shape(arg: Shape) {}
trait繼承
trait允許繼承,例如:
trait Base{}
trait Derived: Base {}
這表示Derived繼承了Base,它意味著,滿足Derived的類型,必然也滿足Base,所以,在針對一個具體類型impl Derived的時候,編譯器也會要求同時impl Base,否則會報編譯錯誤:
trait Base {}
trait Derived: Base {}
struct T;
// error[E0277]: the trait bound `T: Base` is not satisfied
impl Derived for T {}
fn main() {}
常見trait簡介
標(biāo)準(zhǔn)庫中有很多常見且很有用的trait,我們一起學(xué)習(xí)一下。
Display和Debug
Display和Debug的定義如下:
pub trait Display {
fn fmt(&self, f: &mut Formatter) -> Result;
}
pub trait Debug {
fn fmt(&self, f: &mut Formatter) -> Result;
}
這兩個trait主要用在類似println!這樣進行輸出的地方。只有實現(xiàn)了Display的類型,才能用{}格式打印出來;只有實現(xiàn)了Debug的類型,才能用{:?}和{:#?}格式打印出來。它們之間的更多區(qū)別如下:
Display假定了這個類型可以用utf-8格式的字符串表示,它是準(zhǔn)備給最終用戶看的,并不是所有的類型都應(yīng)該實現(xiàn)這個trait。標(biāo)準(zhǔn)庫中還有一個常用的trait叫作std::string::ToString,對于所有實現(xiàn)Display的類型, 都自動實現(xiàn)了這個ToStringtrait,它包含了一個to_string(&self)->String方法。
Debug主要是為了調(diào)試使用,建議所有的作為API的公開類型都應(yīng)當(dāng)實現(xiàn)這個trait,以方便調(diào)試。
PartialOrd/Ord/PartialEq/Eq
我們首先介紹一下全序和偏序的概念。對于集合X中的元素a,b,c:
如果a < b則一定有!(a > b),稱為反對稱性;
如果a < b且b < c則一定有a < c,稱為傳遞性;
對于X中的所有元素,都存在a < b或a > b或者a == b,三者必居其一,稱為完全性。
如果集合中的元素只具備上述前兩條特征,則稱X是偏序;同時具備以上所有特征,則稱X是全序。
Rust設(shè)計了兩個trait來對全序和偏序進行描述,PartialOrd代表偏序,Ord代表全序。只有滿足全序的類型才可以進行排序,像浮點數(shù)這樣的偏序類型就無法排序。同理,PartialEq用來描述只能部分元素進行相等比較,Eq表示全部元素都可以進行相等比較。這樣的設(shè)計可以讓我們在更早的階段發(fā)現(xiàn)錯誤。
Sized
這個trait表示類型是否有大小,它定義在std::marker模塊中,它沒有任何的成員方法,它與普通trait不同,編譯器對它有特殊處理,用戶也不能針對自己的類型實現(xiàn)這個trait。一個類型是否滿足Sized約束完全是由編譯器推導(dǎo)的,用戶無權(quán)指定。
Default
Rust中沒有C++中構(gòu)造函數(shù)的概念,因為相比普通函數(shù),構(gòu)造函數(shù)本身并沒有提供額外的抽象能力,反倒增加了語法上的負擔(dān),因此,Rust推薦使用普通的靜態(tài)函數(shù)作為類型的構(gòu)造器,例如String::new()。對于那種無參數(shù)無錯誤處理的簡單情況,標(biāo)準(zhǔn)庫提供了Default來做統(tǒng)一抽象,其定義如下:
pub trait Default: Sized {
fn default() -> Self;
}
屬性與derive
Rust中有一種語法,屬性(attribute),基本格式類似于#[xxx]。屬性可以用來注釋聲明,類似于Java中的注解。有一種非常實用的屬性derive,可以幫助我們自動impl某些trait,因為實現(xiàn)某些trait的時候,邏輯是非常機械化的,例如Debug。示例如下:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 10,
height: 20,
};
println!("{:?}", rect); // 輸出:Rectangle { width: 10, height: 20 }
}
目前,Rust支持的可以自動derive的trait有以下這些:
Debug Clone Copy Hash RustcEncodable RustcDecodable PartialEq Eq
ParialOrd Ord Default FromPrimitive Send Sync
參考文獻
《Rust編程之道》張漢東
《深入淺出Rust》范長春
總結(jié)
以上是生活随笔為你收集整理的corutine rust_Rust学习笔记#5:函数和trait的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python批量_python 中如何去
- 下一篇: java使用ajax异步刷新_Jquer