官术网_书友最值得收藏!

第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)度。

主站蜘蛛池模板: 秭归县| 潢川县| 信阳市| 郴州市| 丹寨县| 泾源县| 夏津县| 商城县| 叶城县| 沐川县| 磐安县| 金寨县| 寻乌县| 二手房| 定襄县| 西畴县| 屏山县| 固阳县| 乳山市| 剑河县| 柏乡县| 绵竹市| 景东| 铅山县| 昌江| 静安区| 东安县| 金阳县| 安岳县| 咸阳市| 阿克陶县| 汉寿县| 渑池县| 鄂州市| 兖州市| 枝江市| 奉化市| 呼伦贝尔市| 章丘市| 密山市| 兴和县|