第4章 函數(shù)
4.1 簡介
Rust的函數(shù)使用關(guān)鍵字fn開頭。函數(shù)可以有一系列的輸入?yún)?shù),還有一個(gè)返回類型。函數(shù)體包含一系列的語句(或者表達(dá)式)。函數(shù)返回可以使用return語句,也可以使用表達(dá)式。Rust編寫的可執(zhí)行程序的入口就是fn main()函數(shù)。以下是一個(gè)函數(shù)的示例:
fn add1(t : (i32, i32)) -> i32 { t.0 + t.1 }
這個(gè)函數(shù)有一個(gè)輸入?yún)?shù),其類型是tuple (i32, i32)。它有一個(gè)返回值,返回類型是i32。函數(shù)的參數(shù)列表與let語句一樣,也是一個(gè)“模式解構(gòu)”。模式結(jié)構(gòu)的詳細(xì)解釋請參考第7章。上述函數(shù)也可以寫成下面這樣:
fn add2((x, y) : (i32, i32)) -> i32 { x + y }
函數(shù)體內(nèi)部是一個(gè)表達(dá)式,這個(gè)表達(dá)式的值就是函數(shù)的返回值。也可以寫return x+y;這樣的語句作為返回值,效果是一樣的。
函數(shù)也可以不寫返回類型,在這種情況下,編譯器會認(rèn)為返回類型是unit ()。此處和表達(dá)式的規(guī)定是一致的。
函數(shù)可以當(dāng)成頭等公民(first class value)被復(fù)制到一個(gè)值中,這個(gè)值可以像函數(shù)一樣被調(diào)用。示例如下:
fn main() {
let p = (1, 3);
// func 是一個(gè)局部變量
let func = add2;
// func 可以被當(dāng)成普通函數(shù)一樣被調(diào)用
println!("evaluation output {}", func(p));
}
在Rust中,每一個(gè)函數(shù)都具有自己單獨(dú)的類型,但是這個(gè)類型可以自動轉(zhuǎn)換到fn類型。示例如下:
fn main() { // 先讓 func 指向 add1 let mut func = add1; // 再重新賦值,讓 func 指向 add2 func = add2; }
編譯,會出現(xiàn)編譯錯(cuò)誤,如下:
error[E0308]: mismatched types --> test.rs:11:12 | 11 | func = add2; | ^^^^ expected fn item, found a different fn item | = note: expected type `fn((i32, i32)) -> i32 {add1}` found type `fn((i32, i32)) -> i32 {add2}`
雖然add1和add2有同樣的參數(shù)類型和同樣的返回值類型,但它們是不同類型,所以這里報(bào)錯(cuò)了。修復(fù)方案是讓func的類型為通用的fn類型即可:
// 寫法一,用 as 類型轉(zhuǎn)換 let mut func = add1 as fn((i32, i32))->i32; // 寫法二,用顯式類型標(biāo)記 let mut func : fn((i32, i32))->i32 = add1;
以上兩種寫法都能修復(fù)上面的編譯錯(cuò)誤。但是,我們不能在參數(shù)、返回值類型不同的情況下作類型轉(zhuǎn)換,比如:
fn add3(x: i32, y: i32) -> i32 { x + y } fn main() { let mut func : fn((i32, i32))->i32 = add1; func = add2; func = add3; }
這里再加了一個(gè)add3函數(shù),它接受兩個(gè)i32參數(shù),這就跟add1和add2有了本質(zhì)區(qū)別。add1和add2是一個(gè)參數(shù),類型是tuple包含兩個(gè)i32成員,而add3是兩個(gè)i32參數(shù)。三者完全不一樣,它們之間是無法進(jìn)行類型轉(zhuǎn)換的。
另外需要提示的就是,Rust的函數(shù)體內(nèi)也允許定義其他item,包括靜態(tài)變量、常量、函數(shù)、trait、類型、模塊等。比如:
fn test_inner() { static INNER_STATIC: i64 = 42; // 函數(shù)內(nèi)部定義的函數(shù) fn internal_incr(x: i64) -> i64 { x + 1 } struct InnerTemp(i64); impl InnerTemp { fn incr(&mut self) { self.0 = internal_incr(self.0); } } // 函數(shù)體,執(zhí)行語句 let mut t = InnerTemp(INNER_STATIC); t.incr(); println!("{}", t.0); }
當(dāng)你需要一些item僅在此函數(shù)內(nèi)有用的時(shí)候,可以把它們直接定義到函數(shù)體內(nèi),以避免污染外部的命名空間。
4.2 發(fā)散函數(shù)
Rust支持一種特殊的發(fā)散函數(shù)(Diverging functions),它的返回類型是感嘆號!。如果一個(gè)函數(shù)根本就不能正常返回,那么它可以這樣寫:
fn diverges() -> ! { panic! ("This function never returns! "); }
因?yàn)閜anic!會直接導(dǎo)致棧展開,所以這個(gè)函數(shù)調(diào)用后面的代碼都不會繼續(xù)執(zhí)行,它的返回類型就是一個(gè)特殊的!符號,這種函數(shù)也叫作發(fā)散函數(shù)。發(fā)散類型的最大特點(diǎn)就是,它可以被轉(zhuǎn)換為任意一個(gè)類型。比如:
let x : i32 = diverges(); let y : String = diverges();
我們?yōu)槭裁葱枰@樣的一種返回類型呢?先看下面的例子:
let p = if x { panic!("error"); } else { 100 };
上面這條語句中包含一個(gè)if-else分支結(jié)構(gòu)的表達(dá)式。我們知道,對于分支結(jié)構(gòu)的表達(dá)式,它的每條分支的類型必須一致。那么這條panic!宏應(yīng)該生成一個(gè)什么類型呢?這就是!類型的作用了。因?yàn)樗梢耘c任意類型相容,所以編譯器的類型檢查才能通過。
在Rust中,有以下這些情況永遠(yuǎn)不會返回,它們的類型就是!。
? panic!以及基于它實(shí)現(xiàn)的各種函數(shù)/宏,比如unimplemented! 、unreachable!;
? 死循環(huán)loop {};
? 進(jìn)程退出函數(shù)std::process::exit以及類似的libc中的exec一類函數(shù)。
關(guān)于這個(gè)!類型,第8章在對類型系統(tǒng)做更深入分析的時(shí)候還會再提到。
4.3 main函數(shù)
在大部分主流操作系統(tǒng)上,一個(gè)進(jìn)程開始執(zhí)行的時(shí)候可以接受一系列的參數(shù),退出的時(shí)候也可以返回一個(gè)錯(cuò)誤碼。許多編程語言也因此為main函數(shù)設(shè)計(jì)了參數(shù)和返回值類型。以C語言為例,主函數(shù)的原型一般允許定義成以下幾種形式:
int main(void); int main(); int main(int argc, char **argv); int main(int argc, char *argv[]); int main(int argc, char **argv, char **env);
Rust的設(shè)計(jì)稍微有點(diǎn)不一樣,傳遞參數(shù)和返回狀態(tài)碼都由單獨(dú)的API來完成,示例如下:
fn main() { for arg in std::env::args() { println!("Arg: {}", arg); } std::process::exit(0); }
編譯,執(zhí)行并攜帶幾個(gè)參數(shù),可以看到:
$ test -opt1 opt2-- opt3 Arg: test Arg: -opt1 Arg: opt2 Arg: -- Arg: opt3
每個(gè)被空格分開的字符串都是一個(gè)參數(shù)。進(jìn)程可以在任何時(shí)候調(diào)用exit()直接退出,退出時(shí)候的錯(cuò)誤碼由exit()函數(shù)的參數(shù)指定。
如果要讀取環(huán)境變量,可以用std::env::var()以及std::env::vars()函數(shù)獲得。示例如下:
fn main() {
for arg in std::env::args() { match std::env::var(&arg) { Ok(val) => println!("{}: {:? }", &arg, val), Err(e) => println!("couldn't find environment {}, {}", &arg, e), } } println!("All environment varible count {}", std::env::vars().count()); }
var()函數(shù)可以接受一個(gè)字符串類型參數(shù),用于查找當(dāng)前環(huán)境變量中是否存在這個(gè)名字的環(huán)境變量,vars()函數(shù)不攜帶參數(shù),可以返回所有的環(huán)境變量。
此前,Rust的main函數(shù)只支持無參數(shù)、無返回值類型的聲明方式,即main函數(shù)的簽名固定為:fn main() -> ()。但是,在引入了?符號作為錯(cuò)誤處理語法糖之后,就變得不那么優(yōu)雅了,因?yàn)椋糠栆螽?dāng)前所在的函數(shù)返回的是Result類型,這樣一來,問號就無法直接在main函數(shù)中使用了。為了解決這個(gè)問題,Rust設(shè)計(jì)組擴(kuò)展了main函數(shù)的簽名,使它變成了一個(gè)泛型函數(shù),這個(gè)函數(shù)的返回類型可以是任何一個(gè)滿足Terminationtrait約束的類型,其中()、bool、Result都是滿足這個(gè)約束的,它們都可以作為main函數(shù)的返回類型。關(guān)于這個(gè)問題,可以參見第33章。
4.4 const fn
函數(shù)可以用const關(guān)鍵字修飾,這樣的函數(shù)可以在編譯階段被編譯器執(zhí)行,返回值也被視為編譯期常量。示例如下:
#! [feature(const_fn)] const fn cube(num: usize) -> usize { num * num * num } fn main() { const DIM : usize = cube(2); const ARR : [i32; DIM] = [0; DIM]; println!("{:? }", ARR);
cube函數(shù)接受數(shù)字參數(shù),它會返回一個(gè)數(shù)字,而且這個(gè)返回值本身可以用于給一個(gè)const常量做初始化,const常量又可以當(dāng)成一個(gè)常量數(shù)組的長度使用。
const函數(shù)是在編譯階段執(zhí)行的,因此相比普通函數(shù)有許多限制,并非所有的表達(dá)式和語句都可以在其中使用。鑒于目前這個(gè)功能還沒有完全穩(wěn)定,const函數(shù)具體有哪些限制規(guī)則,本書就不在此問題上詳細(xì)展開了,后面也許還會有調(diào)整。
4.5 函數(shù)遞歸調(diào)用
Rust允許函數(shù)遞歸調(diào)用。所謂遞歸調(diào)用,指的是函數(shù)直接或者間接調(diào)用自己。下面用經(jīng)典的Fibonacci數(shù)列來舉例:
fn fib(index: u32) -> u64 { if index == 1 || index == 2 { 1 } else { fib(index -1) + fib(index -2) } } fn main() { let f8 = fib(8); println!("{}", f8); }
這個(gè)fib函數(shù)就是典型的遞歸調(diào)用函數(shù),因?yàn)樵谒暮瘮?shù)體內(nèi)又調(diào)用了它自己。
談到遞歸調(diào)用,許多讀者都會自然聯(lián)想到“尾遞歸優(yōu)化”這個(gè)概念。可惜的是,當(dāng)前版本的Rust暫時(shí)還不支持尾遞歸優(yōu)化,因此如果遞歸調(diào)用層次太多的話,是有可能撐爆棧空間的。不過這個(gè)問題已經(jīng)在設(shè)計(jì)討論之中,各位讀者可以從最新的RFC項(xiàng)目中了解進(jìn)度。
- 大學(xué)計(jì)算機(jī)基礎(chǔ)(第二版)
- CentOS 7 Linux Server Cookbook(Second Edition)
- Flask Web開發(fā)入門、進(jìn)階與實(shí)戰(zhàn)
- Cocos2d-x學(xué)習(xí)筆記:完全掌握Lua API與游戲項(xiàng)目開發(fā) (未來書庫)
- iOS自動化測試實(shí)戰(zhàn):基于Appium、Python與Pytest
- Windows Embedded CE 6.0程序設(shè)計(jì)實(shí)戰(zhàn)
- Python 3.7從入門到精通(視頻教學(xué)版)
- MATLAB GUI純代碼編寫從入門到實(shí)戰(zhàn)
- Arduino機(jī)器人系統(tǒng)設(shè)計(jì)及開發(fā)
- Docker:容器與容器云(第2版)
- PHP+MySQL Web應(yīng)用開發(fā)教程
- C/C++代碼調(diào)試的藝術(shù)(第2版)
- 現(xiàn)代JavaScript編程:經(jīng)典范例與實(shí)踐技巧
- Flask Web開發(fā)實(shí)戰(zhàn):入門、進(jìn)階與原理解析
- Java EE框架開發(fā)技術(shù)與案例教程