深入了解Rust中函數(shù)與閉包的使用
閉包
Rust 的閉包由一個匿名函數(shù)加上外層的作用域組成,舉個例子:
fn?main()?{ ????let?closure?=?|n:?u32|?->?u32?{ ????????n?*?2 ????}; ????println!("n?*?2?=?{}",?closure(12)); ????//?n?*?2?=?24 }
閉包可以被保存在一個變量中,然后我們注意一下它的語法,參數(shù)定義、返回值定義都和普通函數(shù)一樣,但閉包使用的是兩個豎線。我們對比一下兩者的區(qū)別:
//?普通函數(shù)定義 fn?func1(a:?u32,?b:?u32)?->?String?{ ????//?函數(shù)體 } /*?如果換成閉包的話,那么等價于 let?func1?=?|a:?u32,?b:?u32|?->?String?{ ????//?函數(shù)體 } */
所以兩者在語法上沒有什么本質(zhì)的區(qū)別,但這個時候可能有人好奇了,我們能不能把閉包中的匿名函數(shù)換成普通函數(shù)呢?來試一下。
fn?main()?{ ????let?closure1?=?|n:?u32|?->?u32?{ ????????n?*?2 ????}; ????fn?closure2(n:?u32)?->?u32?{ ????????n?*?2 ????} ????println!("n?*?2?=?{}",?closure1(12)); ????println!("n?*?2?=?{}",?closure2(12)); ????/* ????n?*?2?=?24 ????n?*?2?=?24 ????*/ }
從表面上來看是可以的,但其實還存在問題,因為 closure2 只是一個在函數(shù)里定義的函數(shù)而已。而閉包除了要包含函數(shù)之外,還要包含函數(shù)所在的外層作用域,什么意思呢?我們舉例說明:
你看到了什么?沒錯,在函數(shù) closure2 內(nèi)部無法使用外層作用域中的變量 a,因此它只是定義在 main 函數(shù)里的函數(shù)而已,而不是閉包,因為它不包含外層函數(shù)(main)的作用域。
而 Rust 提示我們使用 || { ... },那么 closure1 顯然是閉包,因為它除了包含一個函數(shù)(匿名),還包含了外層作用域,我們將這個閉包賦值給了 closure1。
此外閉包還有一個重要的用途,就是在多線程編程時,可以將主線程的變量移動到子線程內(nèi)部。
關(guān)于多線程后續(xù)會詳細(xì)說,這里只是舉個例子。
//?導(dǎo)入線程模塊 use?std::thread; fn?main()?{ ????let?s?=?String::from("hello?world"); ????//?必須在?||?的前面加上?move ????//?它的含義就是將值從主線程移動到子線程 ????let?closure1?=?move?||?{ ????????println!("{}",?s); ????}; ????//?開啟一個子線程 ????thread::spawn(closure1).join(); ????/* ????hello?world ????*/ }
打印是發(fā)生在主線程當(dāng)中的,而不是子線程,以上就是閉包相關(guān)的內(nèi)容。
高階函數(shù)
了解完閉包之后,再來看看高階函數(shù),在數(shù)學(xué)和計算機中,高階函數(shù)是至少滿足下列一個條件的函數(shù):
- 接收一個或多個函數(shù)作為輸入;
- 輸出一個函數(shù);
在數(shù)學(xué)中它們也叫算子或者泛函,高階函數(shù)是函數(shù)式編程中非常重要的一個概念。
先來看看如何定義一個接收函數(shù)作為參數(shù)的函數(shù):
//?calc?接收三個參數(shù),返回一個?i32 //?參數(shù)一:接收兩個 i32 返回一個 i32 的函數(shù) //?參數(shù)二?和?參數(shù)三均是一個?i32 fn?calc(method:?fn(i32,?i32)?->?i32, ????????a:?i32,?b:?i32)?->?i32?{ ????method(a,?b) } fn?add(a:?i32,?b:?i32)?->?i32?{ ????a?+?b } fn?main()?{ ????println!("a?+?b?=?{}",?calc(add,?12,?33)); ????/* ????a?+?b?=?45 ????*/ ????//?也可以傳遞一個匿名函數(shù),但它不能引用外層作用域的變量 ????//?因為?calc?第一個參數(shù)接收的是函數(shù),不是閉包 ????let?sub?=?|a:?i32,?b:?i32|?->?i32?{ ????????a?-?b ????}; ????println!("a?-?b?=?{}",?calc(sub,?12,?33)); ????/* ????a?-?b?=?-21 ????*/ }
以函數(shù)作為參數(shù),在類型聲明中我們不需要寫函數(shù)名以及參數(shù)名,只需要指明參數(shù)類型、數(shù)量和返回值類型即可。
然后再觀察一下函數(shù) calc 的定義,由于第一個參數(shù) method 接收一個函數(shù),所以它的定義特別的長,我們能不能簡化一下呢?
//?相當(dāng)于給類型起了一個別名 type?Method?=?fn(i32,?i32)?->?i32; fn?calc(method:?Method, ????????a:?i32,?b:?i32)?->?i32?{ ????method(a,?b) }
這種做法也是可以的。
看完了接收函數(shù)作為參數(shù),再來看看如何將函數(shù)作為返回值。
type?Method?=?fn(i32,?i32)?->?i32; //?想要接收字符串的話 //?應(yīng)該使用引用?&String?或切片?&str //?當(dāng)然我們前面說過,更推薦切片 fn?calc(op:?&str)?->?Method?{ ????fn?add(a:?i32,?b:?i32)?->?i32?{ ????????a?+?b ????} ???? ????let?sub?=?|a:?i32,?b:?i32|?->?i32?{?a?-?b?}; ????//?使用?if?else?也是可以的 ????match?op?{ ????????"add"?=>?add, ????????"sub"?=>?sub, ????????//?內(nèi)置的宏,會拋出一個錯誤,表示方法沒有實現(xiàn) ????????_?=>?unimplemented!(), ????} // 注意:此處不可以加分號,因為要作為表達式返回 } fn?main()?{ ????let?(a,?b)?=?(11,?33); ????println!("a?+?b?=?{}",?calc("add")(a,?b)); ????println!("a?-?b?=?{}",?calc("sub")(a,?b)); ????/* ????a?+?b?=?44 ????a?-?b?=?-22 ????*/ }
以上就是高階函數(shù),還是很好理解的,和 Python 比較類似。你可以基于這個特性,實現(xiàn)一個裝飾器,只是 Rust 里面沒有 @ 這個語法糖罷了。這里我們簡單地實現(xiàn)一下吧,加深一遍印象。
enum?Result?{ ????Text(String), ????Func(fn()?->?String), } fn?index()?->?String?{ ????String::from("歡迎來到古明地覺的編程教室") } fn?login_required(username:?&str,?password:?&str)?->?Result?{ ????if?!(username?==?"satori"?&&?password?==?"123")?{ ????????return?Result::Text(String::from("請先登錄")); ????}?else?{ ????????return?Result::Func(index); ????} } fn?main()?{ ????let?res1?=?login_required("xxx",?"yyy"); ????let?res2?=?login_required("satori",?"123"); ????//?如果后續(xù)還要使用?res1?和?res2,那么就使用引用 ????//?也就是?[&res1,?&res2] ????//?但這里我們不用了,所以是?[res1,?res2],此時會轉(zhuǎn)移所有權(quán) ????for?item?in?[res1,?res2]?{ ????????match?item?{ ????????????Result::Text(error)?=>?println!("{}",?error), ????????????Result::Func(index)?=>?println!("{}",?index()), ????????} ????} ????/* ????請先登錄 ????歡迎來到古明地覺的編程教室 ????*/ }
是不是很有趣呢?這里再次看到了枚舉類型的威力,我們有可能返回字符串,也有可能返回函數(shù),那么應(yīng)該怎么辦呢?很簡單,將它們放到枚舉里面即可,這樣它們都是枚舉類型。至于到底是哪一個成員,再基于 match 分別處理即可。
還記得 match 嗎?match 可以有任意多個分支,每一個分支都應(yīng)該返回相同的類型,并且只有一個分支會執(zhí)行成功,然后該分支的返回值會作為整個 match 表達式的返回值。
發(fā)散函數(shù)
最后再來看看發(fā)散函數(shù),這個概念在其它語言里面應(yīng)該很少聽到。在 Rust 里面,發(fā)散函數(shù)永遠(yuǎn)不會返回,它的返回值被標(biāo)記為 !,表示這是一個空類型。
//?發(fā)散函數(shù)的返回值類型是一個感嘆號 //?它表示這個函數(shù)執(zhí)行時會報錯 fn?foo()?->?!?{ ????panic!("這個函數(shù)執(zhí)行時會報錯") } fn?main()?{ ????//?調(diào)用發(fā)散函數(shù)時,可以將其結(jié)果賦值給任意類型的變量 ????let?res1:?u32?=?foo(); ????let?res2:?f64?=?foo(); }
所以這個發(fā)散函數(shù)沒啥卵用,你在實際開發(fā)中估計一輩子也用不上,因為它在執(zhí)行的時候會 panic 掉。所以這段代碼編譯的時候是沒有問題的,但執(zhí)行時會觸發(fā) panic。既然執(zhí)行時會報錯,那么當(dāng)然可以賦值給任意類型的變量。
因此當(dāng)返回值類型為 ! 時,我們需要通過 panic 宏讓函數(shù)在執(zhí)行的過程中報錯。但要注意的是,發(fā)散函數(shù)和不指定返回值的函數(shù)是不一樣的,舉個例子:
//?發(fā)散函數(shù)的返回值類型是一個感嘆號 //?它表示這個函數(shù)執(zhí)行時會報錯 fn?foo()?->?!?{ ????panic!("這個函數(shù)執(zhí)行時會報錯"); } //?不指定返回值,默認(rèn)返回?() //?所以以下等價于?fn?bar()?->?()?{} //?但很明顯?bar?函數(shù)是有返回值的,會返回空元組 fn?bar()?{ }
總的來說發(fā)散函數(shù)沒啥卵用,在工作中也不建議使用,只要知道有這么個東西就行。
到此這篇關(guān)于深入了解Rust中函數(shù)與閉包的使用的文章就介紹到這了,更多相關(guān)Rust函數(shù) 閉包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust 中的 Packages 與 Crates模塊化構(gòu)建的基礎(chǔ)及開發(fā)流程
Rust中的Crate是編譯器處理的最小代碼單元,可以是二進制或庫,每個Crate由一個CrateRoot文件(通常是src/main.rs或src/lib.rs)定義,本文給大家介紹Rust 中的 Packages 與 Crates模塊化構(gòu)建的基礎(chǔ)及開發(fā)流程,感興趣的朋友一起看看吧2025-02-02