【譯】理解Rust中的Futures(二)
原文標題:Understanding Futures in Rust -- Part 2
原文連結:https://www.viget.com/articles/understanding-futures-is-rust-part-2/
公眾號: Rust 碎碎念
翻譯 by: Praying
背景
如果你還沒有看前面的內容,可以在這裡[1]檢視(譯註:已有譯文,可在公眾號檢視)。
在第一部分,我們介紹了 Future trait,瞭解了 future 是如何被建立和執行的,並且開始知道它們如何能被連結到一起。
上次內容的程式碼可以在這個 playground 連結[2]檢視,並且本文中所有示例程式碼將會以這段程式碼為基礎。
注意:所有的程式碼示例都有對應的 playground 連結,其中一些用於解釋說明但無法編譯的程式碼會有相應的標記。
目標
如果你熟悉 JavaScript 中的 promise 並且閱讀了最新的部落格,你可能會對先前文章中提到的組合子(then
、catch
和finally
)感到困惑。
你將會在本文章找到與它們對等的東西,並且在最後,下面這段程式碼將能夠編譯。你將會理解使得 future 能夠運作的型別,trait 和底層概念。
// This does not compile, yet
fn main() {
let my_future = future::ready(1)
.map(|x| x + 3)
.map(Ok)
.map_err(|e: ()| format!("Error: {:?}", e))
.and_then(|x| future::ready(Ok(x - 3)))
.then(|res| {
future::ready(match res {
Ok(val) => Ok(val + 3),
err => err,
})
});
let val = block_on(my_future);
assert_eq!(val, Ok(4));
}
工具函式
首先,我們需要一些工具函式,future::ready
和block_on
。這些函式能夠讓我們很容易地建立和執行 future 直到它們完成,這些函式雖然有用,但是在生產環境的程式碼中並不常見。
在開始之前,我們先把我們的Future
trait 和Context
結構體整合到模組裡以免和標準庫衝突。
mod task {
use crate::NOTIFY;
pub struct Context<'a> {
waker: &'a Waker,
}
impl<'a> Context<'a> {
pub fn from_waker(waker: &'a Waker) -> Self {
Context { waker }
}
pub fn waker(&self) -> &'a Waker {
&self.waker
}
}
pub struct Waker;
impl Waker {
pub fn wake(&self) {
NOTIFY.with(|f| *f.borrow_mut() = true)
}
}
}
use crate::task::*;
mod future {
use crate::task::*;
pub enum Poll<T> {
Ready(T),
Pending,
}
pub trait Future {
type Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output>;
}
}
use crate::future::*;
Playground 連結[3]
這裡唯一需要注意的就是,只有將模組,型別和函式公開,才能在程式碼中使用它們。這可以通過pub
關鍵字來完成。
工具函式實現
Future::Ready
future::ready
建立了一個 future,該 future 帶有傳入值並且是立即就緒(ready)的。當你有一個已經不是 future 的值的時候,這個函式可以用於開啟一個 future 鏈,就像前一個示例那樣。
mod future {
// ...
pub struct Ready<T>(Option<T>);
impl<T> Future for Ready<T> {
type Output = T;
fn poll(&mut self, _: &Context) -> Poll<Self::Output> {
Poll::Ready(self.0.take().unwrap())
}
}
pub fn ready<T>(val: T) -> Ready<T> {
Ready(Some(val))
}
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", run(my_future));
}
Playground 連結[4]
我們建立了一個型別為Ready<T>
的泛型結構體,該結構體包裝了一個Option
。這裡我們使用Option
列舉以保證 poll 函式只被呼叫一次。在 executor 的實現中,在返回一個Poll::Ready
之後呼叫 poll 將會報錯。
BLOCK_ON
為了我們的目標,我們把我們的 run 函式重新命名為block_on
。在future-preview
這個 crate 中,該函式使用內部的LocalPool
來執行一個 future 直到完成,同時會阻塞當前執行緒。我們的函式也做了相似的事情。
fn block_on<F>(mut f: F) -> F::Output
where
F: Future,
{
NOTIFY.with(|n| loop {
if *n.borrow() {
*n.borrow_mut() = false;
let ctx = Context::from_waker(&Waker);
if let Poll::Ready(val) = f.poll(&ctx) {
return val;
}
}
})
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", block_on(my_future));
}
Playground 連結[5]
組合子(Combinators)
首先,讓我們從一些能夠讓你直接作用於另一個 Future 的Output
值的一些組合子開始。在本文中,我們使用非正式的但是比較流行的組合子定義,即能夠允許你對某種型別執行操作,並與其他型別結合起來的函式。例如,一個巢狀的 future 可以由一個組合子函式函式建立,它可以有一個複雜的型別Future< Output = Future < Output = i32>>
。這可以被稱為一個 future,該 future 的輸出(Output)是另一個 future,新的 future 的輸出是 i32 型別。這樣的組合子中,最簡單的一個就是map
。
Map
如果你熟悉Result
或者Option
型別的map
函式,那麼對它應該不陌生。map 組合子持有一個函式並將其應用到 future 的Output
值上,返回一個新的 future,這個新 future 把函式的結果(Result)作為它的Output
。Future 中的 map 組合子甚至比Result
或者Option
中更簡單,因為不需要考慮 failure 的情況。map 就是簡單的Future->Future
。
下面是函式簽名:
// does not compile
fn map<U, F>(self: Sized, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
map
是一個泛型函式,它接收一個閉包,返回一個實現了 Future 的Map
結構體。不是每當我們在值上進行連結都需要實現Future
trait,正如我們在最後一部分做的那樣,我們可以使用這些函式來為我們完成這些工作。
讓我們來分析一下:
Map<Self, F>
聲明瞭 map 函式的(返回)型別,包括當前的 future,以及傳入函式的 future。where
是一個能夠讓我們新增型別約束的關鍵字。對於F
型別引數,我們可以在內部定義約束map<U, F: FnOnce(Self::Output) -> U
,但是使用 where 語句可讀性會更好。FnOnce(Self::Output) -> U
是一個函式的型別定義,該函式接收當前型別的Output
並返回任意型別U
。FnOnce
是函式 trait 中的一個,其他還包括FnMut
和Fn
。FnOnce
是用起來最簡單的,因為編譯器可以保證這個函式只被呼叫一次。它使用環境中用到的值並獲取其所有權。Fn
和FnMut
分別以不可變和可變的方式借用環境中值的引用。所有的閉包都實現了FnOnce
trait,並且其中一些沒有移動值的閉包還實現了FnMut
和Fn
trait。這是 Rust 做的最酷的事情之一,允許對閉包和第一類函式引數進行真正有表達力的使用。Rust book 中的相關內容[6]值得一讀。Self: Sized
是一個約束,允許map
只能被Sized
的 trait 實現者呼叫。你不必考慮這個問題,但是確實有些型別不是Sized
。例如,[i32]
是一個不確定大小的陣列。因為我們不知道它多長。如果我們想要為它實現我們的Future
trait,那麼我們就不能對它呼叫map
。
大多數組合子都遵循這個模式,因此接下來的文章我們就不需要分析的這麼仔細了。
下面是一個map
的完整實現,它的Map
型別以及它對Future
的實現
mod future {
trait Future {
// ...
fn map<U, F>(self, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
{
Map {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, F, T> Future for Map<Fut, F>
where
Fut: Future,
F: FnOnce(Fut::Output) -> T,
{
type Output = T;
fn poll(&mut self, cx: &Context) -> Poll<T> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
Poll::Ready(f(val))
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1).map(|val| val + 1);
println!("Output: {}", block_on(my_future));
}
Playground 連結[7]
從高層次來講,當我們呼叫一個 future 上的map
時,我們構造了一個Map
型別,該型別持有當前 future 的引用以及我們傳入的閉包。Map
物件自身也是一個 Future。當它被輪詢時,它依次輪詢底層的 future。當底層的 future 就緒後,它獲取那個 future 的Output
的值並且把它傳入閉包,對Poll::Ready
中的閉包返回的值進行包裝(wrapping)並且把新值向上傳遞。
如果你閱讀了最新的部落格,你對在這裡看到的東西應該感到很熟悉,但是在我們繼續之前,我會快速地講解作為一個複習。
pub struct Map<Fut, F>
是一個關於 future——Fut
和函式F
的泛型。f: Option<F>
是一個包裝了閉包了Option
型別。這裡是個小技巧,以保證閉包只被呼叫一次。當你獲取一個Option
的值,它會用None
替換內部的值並且返回裡面包含的值。如果在返回一個Poll::Ready
之後被輪詢,這個函式會 panic。在實際中,future 的 executor 不會允許這種情況發生。type Output = T;
定義了 map future 的輸出和我們的閉包的返回值是將會是相同的。Poll::Read(f(val))
返回帶有閉包返回結果的就緒(ready)狀態。Poll::Pending => Poll::Pending
如果底層的 future 返回 pending,繼續傳遞。future::ready(1).map(|val| val + 1);
這對就緒(ready)future 的輸出進行了 map,並對其加 1。它返回了一個 map future,其中帶有對原先的 future 的一個引用。map future 在執行期間輪詢原先的 future 是否就緒(ready)。這和我們的AddOneFuture
做的是相同的事情。
這真的很酷,主要有以下幾個原因。首先,你不必對每一個你想要進行的計算都實現一個新的 future,它們可以被包裝(wrap)進組合子。事實上,除非你正在實現你自己的非同步操作,否則你可能從來都不需要自己去實現Future
trait。
Then
現在我們有了map
,我們可以把任何我們想要的計算連結起來,對麼?答案是對的,但是對此還有一個相當大的警告。
想象一下,當你有一些函式,這些函式返回你想要連結起來的 future。對於這個例子,我們可以想象,它們是下面的 api 呼叫,這些呼叫返回包裝(wrap)在 future 中的結果,get_user
和get_files_for_user
。
// does not compile
fn main() {
let files_future = get_user(1).map(|user| get_files_for_user(user));
println!("User Files: {}", block_on(files_future));
}
這段程式碼無法編譯,但是你可以想象你在這裡構建的型別,看起來應該像這樣:Future<Output = Future<Output= FileList>>
。這在使用Result
和Option
型別的時候也是一個常見問題。使用map
函式經常會導致巢狀的輸出和對這些巢狀的繁瑣處理。在這種情況下,你不得不去跟蹤到底嵌套了多少層並且對每一個巢狀的 future 都呼叫block_on
。
幸運地是,Result
,Option
有一個被稱為and_then
的解決方案。Option
的and_then
通過對T
應用一個函式來對映(map)Some(T) -> Some(U)
,並且返回閉包所返回的Option
。對於 future,它是通過一個稱為then
的函式來實現的,該函式看起來很像對映(map),但是這個閉包應該它自己的 future。在一些語言中,這被稱為flatmap
。這裡值得注意的是,傳遞給then
的閉包返回的值必須是實現了Future
,否則你將會得到一個編譯器錯誤。
這裡是我們的對於then
,Then
結構體和它的對Future
trait 的實現。其中的大部分內容和我們在 map 中做的很像。
mod future {
trait Future {
// ...
fn then<Fut, F>(self, f: F) -> Then<Self, F>
where
F: FnOnce(Self::Output) -> Fut,
Fut: Future,
Self: Sized,
{
Then {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Then<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, NextFut, F> Future for Then<Fut, F>
where
Fut: Future,
NextFut: Future,
F: FnOnce(Fut::Output) -> NextFut,
{
type Output = NextFut::Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
f(val).poll(cx)
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1));
println!("Output: {}", block_on(my_future));
}
Playground 連結[8]
這裡面沒見過的程式碼可能是f(val).poll(cx)
。它呼叫了帶有先前 future 的閉包並且直接返回給你poll
的值。
聰明的你可能會意識到,我們的
原文標題:Understanding Futures In Rust -- Part 1
原文連結:https://www.viget.com/articles/understanding-futures-in-rust-part-1/
公眾號: Rust 碎碎念
翻譯 by: Praying
背景
原文標題:Understanding Futures in Rust -- Part 2
原文連結:https://www.viget.com/articles/understanding-futures-is-rust-part-2/
公眾號: Rust 碎碎念
翻譯 by: Praying
背景
原文標題:Understanding Closures in Rust
原文連結:https://medium.com/swlh/understanding-closures-in-rust-21f286ed1759
公眾號: Rust 碎碎念
翻譯 by: Praying
概要
閉包(closu
原文標題:Understanding Partial Moves in Rust
原文連結:https://whileydave.com/2020/11/30/understanding-partial-moves-in-rust/
公眾號: Rust 碎碎念
翻譯 by: Praying
最近,我
原文地址:Understanding JavaScript’s ‘undefined’
原文作者:Angus Croll
譯文出自:掘金翻譯計劃
本文永久連結:github.com/xitu/gold-m…
譯者:yanyixin
校對者:noahziheng, M 譯文開始
函數語言程式設計是一種程式設計風格,這種程式設計風格就是試圖將傳遞函式作為引數(即將作為回撥函式)和返回一個函式,但沒有函式副作用(函式副作用即會改變程式的狀態)。
有很多語言採用這種程式設計風格,其中包括JavaScript、Haskell、Clojure、Erlang和Scala等一些很流行的程
原文標題:Why doesn't Rust's BTreeMap have a with_capacity() method?
原文連結:https://www.nicolas-hahn.com/2020/11/30/btreemap-with-capacity/
公眾號: Rust 碎碎念
翻譯
原文標題:Study of std::io::Error
原文連結:https://matklad.github.io/2020/10/15/study-of-std-io-error.html
公眾號: Rust 碎碎念
翻譯 by: Praying
在本文中,我們將剖析 Rust 標準庫中的std engine 場景 aud 初始 cto onf 不變 addchild ems
效果圖:
程序分析:
初始化GameLayer場景觸摸。背景、音樂、UI及定時間器
bool GameLayer::init()
{
if (!CCLayer::init()) unknown nds end shell腳本 其他 你是 結構 lan isp 關於Linux talk:http://man.linuxde.net/talk
下面文章轉自:http://www.oschina.net/translate/11-lesser-know mat 復雜 任務 遙控 一次 環境 box range 使用
詳解VBA編程是什麽
由 vietdung90 創建,最後一次修改 2016-10-19
直到 90 年代早期,使應用程序自動化還是充滿挑戰性的領域.對每個需要自動化的應用程序,人們不得不學習一種不 本節內容
深淺拷貝
迴圈方式
字典常用方法總結
一、深淺拷貝
列表、元組、字典(以及其他)
對於列表、元組和字典而言,進行賦值(=)、淺拷貝(copy)、深拷貝(deepcopy)而言,其記憶體地址是變化不通的。
賦值(=)
賦值只是建立一個變數,該變數指向原來的記憶體地址
同步 mage 不用 start 技術分享 ted err 斷線 情況 AQS在Worker中的應用
我對這個上鎖一直搞不懂,雖然有註釋說是允許中斷啥的,但是還是一頭霧水,就打算直接看代碼分析。第一眼看到這個lock的時候,我就嚇到了。啥,一上鎖,多個線程不是就要同步排隊了 本節內容
1、概述
2、socket例項
3、總結
一、概述
之前我們只是介紹了soket的概念和一些邏輯圖表,下面我們來看看,socket的客戶端和服務端到底是怎麼用的?
二、socket例項
2.1 客戶端
2.1.1 客戶端程式碼邏輯圖
2.1.2 客戶端程式碼
1.定義
vector<vector<int>>a;
2.初始化
a.resize(row,vector<int>(column)); a.resize(row);for (int i = 0; i < row;
原理:
只要竊聽者對量子通道進行測量,就會改變正在傳輸的量子位,比如傳輸的是“向上”,竊聽者有兩種可能進行:若竊聽者拿B2(即對角基態)去測量(50%的概率),那麼會得到我們之前看到的四分之一波片濾光後的結果,“向上”分解成右斜45度 pac col problem 一個 style pro edit spa isp
階乘因式分解(二)
時間限制:3000 ms | 內存限制:65535 KB
難度:3
描述
給定兩個數n,m,其中m是一個素數。
將n(0<=n<=2^31)的階乘
LVDS介面型別
一種是HR bank的LVDS_25,Vcco=2.5V,也就是通常說的LVDS介面。
The LVDS_25 I/O standard is only available in the HR I/O banks. It requires a VCC
下載CLion最新版本
CLion是一款專為開發C及C++所設計的跨平臺IDE。它是以IntelliJ為基礎設計的,包含了許多智慧功能來提高開發人員的生產力。這種強大的IDE幫助開發人員在Linux、OS X和Windows上來開發C/C++,同時它還使用智慧編輯器來提高程式碼質量、自動程式碼重
時時要有危機意識。別以為你命好運也好。
1.題目:打印出所有的 水仙花數 ,所謂 水仙花數 是指一個三位數,其各位數字立方和等於該數本身。
例如:153是一個 水仙花數 ,因為153=1的三次方+5的三次方+3的三次方。
題非常簡單,只要用for迴圈,取出數的個位、十位 Then::poll
函式可能會 panic。如果第一個 future 返回就緒(ready)但是第二個 future 返回Poll::Pending
,接著let f = self.f.take().unwrap();
這行程式碼就會在下次被輪詢(poll)的時候 panic 並退出程式。在future-preview
中,這種情況會通過一個稱為Chain[9]的型別來處理。Chain 通過 unsafe 程式碼塊來實現,並且使用了新型別——Pin
。這些內容超出了本文的範圍。目前來講,我們可以假定任何通過
相關推薦
【譯】理解Rust中的Futures (一)
【譯】理解Rust中的Futures(二)
【譯】理解Rust中的閉包
【譯】理解Rust中的區域性移動
【譯】理解 JavaScript 中的 undefined
【譯】理解JavaScript中的柯里化
【譯】為什麼Rust中的BTreeMap沒有with_capacity()方法?
【譯】對Rust中的std::io::Error的研究
【雷電】源代碼分析(二)-- 進入遊戲攻擊
【Linux】好玩的Linux命令(二)
【轉】VBA編程入門(二)
Python基礎【day03】:字典進階(二)
【筆記】ThreadPoolExecutor源碼閱讀(二)
網路程式設計基礎【day08】:簡單socket例項(二)
【C++】Vector的陣列用法(二)
【編碼】量子密碼學習筆記(二)
【ACM】階乘因式分解(二)
【電路】差分介面互連(二)
【更新】CLion v2018.3釋出(二):CPU Profiler和C++ Support
【精選】JAVA入門演算法題(二)