網站首頁 編程語言 正文
閉包
Rust 的閉包由一個匿名函數加上外層的作用域組成,舉個例子:
fn?main()?{
????let?closure?=?|n:?u32|?->?u32?{
????????n?*?2
????};
????println!("n?*?2?=?{}",?closure(12));
????//?n?*?2?=?24
}
閉包可以被保存在一個變量中,然后我們注意一下它的語法,參數定義、返回值定義都和普通函數一樣,但閉包使用的是兩個豎線。我們對比一下兩者的區別:
//?普通函數定義
fn?func1(a:?u32,?b:?u32)?->?String?{
????//?函數體
}
/*?如果換成閉包的話,那么等價于
let?func1?=?|a:?u32,?b:?u32|?->?String?{
????//?函數體
}
*/
所以兩者在語法上沒有什么本質的區別,但這個時候可能有人好奇了,我們能不能把閉包中的匿名函數換成普通函數呢?來試一下。
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 只是一個在函數里定義的函數而已。而閉包除了要包含函數之外,還要包含函數所在的外層作用域,什么意思呢?我們舉例說明:
你看到了什么?沒錯,在函數 closure2 內部無法使用外層作用域中的變量 a,因此它只是定義在 main 函數里的函數而已,而不是閉包,因為它不包含外層函數(main)的作用域。
而 Rust 提示我們使用?|| { ... },那么 closure1 顯然是閉包,因為它除了包含一個函數(匿名),還包含了外層作用域,我們將這個閉包賦值給了 closure1。
此外閉包還有一個重要的用途,就是在多線程編程時,可以將主線程的變量移動到子線程內部。
關于多線程后續會詳細說,這里只是舉個例子。
//?導入線程模塊
use?std::thread;
fn?main()?{
????let?s?=?String::from("hello?world");
????//?必須在?||?的前面加上?move
????//?它的含義就是將值從主線程移動到子線程
????let?closure1?=?move?||?{
????????println!("{}",?s);
????};
????//?開啟一個子線程
????thread::spawn(closure1).join();
????/*
????hello?world
????*/
}
打印是發生在主線程當中的,而不是子線程,以上就是閉包相關的內容。
高階函數
了解完閉包之后,再來看看高階函數,在數學和計算機中,高階函數是至少滿足下列一個條件的函數:
- 接收一個或多個函數作為輸入;
- 輸出一個函數;
在數學中它們也叫算子或者泛函,高階函數是函數式編程中非常重要的一個概念。
先來看看如何定義一個接收函數作為參數的函數:
//?calc?接收三個參數,返回一個?i32
//?參數一:接收兩個 i32 返回一個 i32 的函數
//?參數二?和?參數三均是一個?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
????*/
????//?也可以傳遞一個匿名函數,但它不能引用外層作用域的變量
????//?因為?calc?第一個參數接收的是函數,不是閉包
????let?sub?=?|a:?i32,?b:?i32|?->?i32?{
????????a?-?b
????};
????println!("a?-?b?=?{}",?calc(sub,?12,?33));
????/*
????a?-?b?=?-21
????*/
}
以函數作為參數,在類型聲明中我們不需要寫函數名以及參數名,只需要指明參數類型、數量和返回值類型即可。
然后再觀察一下函數 calc 的定義,由于第一個參數 method 接收一個函數,所以它的定義特別的長,我們能不能簡化一下呢?
//?相當于給類型起了一個別名
type?Method?=?fn(i32,?i32)?->?i32;
fn?calc(method:?Method,
????????a:?i32,?b:?i32)?->?i32?{
????method(a,?b)
}
這種做法也是可以的。
看完了接收函數作為參數,再來看看如何將函數作為返回值。
type?Method?=?fn(i32,?i32)?->?i32;
//?想要接收字符串的話
//?應該使用引用?&String?或切片?&str
//?當然我們前面說過,更推薦切片
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,
????????//?內置的宏,會拋出一個錯誤,表示方法沒有實現
????????_?=>?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
????*/
}
以上就是高階函數,還是很好理解的,和 Python 比較類似。你可以基于這個特性,實現一個裝飾器,只是 Rust 里面沒有 @ 這個語法糖罷了。這里我們簡單地實現一下吧,加深一遍印象。
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");
????//?如果后續還要使用?res1?和?res2,那么就使用引用
????//?也就是?[&res1,?&res2]
????//?但這里我們不用了,所以是?[res1,?res2],此時會轉移所有權
????for?item?in?[res1,?res2]?{
????????match?item?{
????????????Result::Text(error)?=>?println!("{}",?error),
????????????Result::Func(index)?=>?println!("{}",?index()),
????????}
????}
????/*
????請先登錄
????歡迎來到古明地覺的編程教室
????*/
}
是不是很有趣呢?這里再次看到了枚舉類型的威力,我們有可能返回字符串,也有可能返回函數,那么應該怎么辦呢?很簡單,將它們放到枚舉里面即可,這樣它們都是枚舉類型。至于到底是哪一個成員,再基于 match 分別處理即可。
還記得 match 嗎?match 可以有任意多個分支,每一個分支都應該返回相同的類型,并且只有一個分支會執行成功,然后該分支的返回值會作為整個 match 表達式的返回值。
發散函數
最后再來看看發散函數,這個概念在其它語言里面應該很少聽到。在 Rust 里面,發散函數永遠不會返回,它的返回值被標記為?!,表示這是一個空類型。
//?發散函數的返回值類型是一個感嘆號
//?它表示這個函數執行時會報錯
fn?foo()?->?!?{
????panic!("這個函數執行時會報錯")
}
fn?main()?{
????//?調用發散函數時,可以將其結果賦值給任意類型的變量
????let?res1:?u32?=?foo();
????let?res2:?f64?=?foo();
}
所以這個發散函數沒啥卵用,你在實際開發中估計一輩子也用不上,因為它在執行的時候會 panic 掉。所以這段代碼編譯的時候是沒有問題的,但執行時會觸發 panic。既然執行時會報錯,那么當然可以賦值給任意類型的變量。
因此當返回值類型為?!?時,我們需要通過 panic 宏讓函數在執行的過程中報錯。但要注意的是,發散函數和不指定返回值的函數是不一樣的,舉個例子:
//?發散函數的返回值類型是一個感嘆號
//?它表示這個函數執行時會報錯
fn?foo()?->?!?{
????panic!("這個函數執行時會報錯");
}
//?不指定返回值,默認返回?()
//?所以以下等價于?fn?bar()?->?()?{}
//?但很明顯?bar?函數是有返回值的,會返回空元組
fn?bar()?{
}
總的來說發散函數沒啥卵用,在工作中也不建議使用,只要知道有這么個東西就行。
原文鏈接:https://mp.weixin.qq.com/s/j0cQm8aDzV7Kx6HSOrEKDQ
相關推薦
- 2022-07-01 Oracle數據庫用戶密碼過期的解決方法_oracle
- 2023-11-18 Python將字符串String轉換成要使用的變量
- 2022-11-14 Python實現簡易超市管理系統_python
- 2022-08-02 C#中的一些延時函數_C#教程
- 2022-03-19 CentOS7下安裝MongoDB數據庫過程_MongoDB
- 2022-04-17 mac 使用zsh vscode和終端node默認版本不一致 (nvm node版本管理工具)
- 2022-05-15 Python使用chrome配置selenium操作詳解_python
- 2022-01-09 el-tree同級節點可選擇 其他節點及父節點禁用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支