老熟女激烈的高潮_日韩一级黄色录像_亚洲1区2区3区视频_精品少妇一区二区三区在线播放_国产欧美日产久久_午夜福利精品导航凹凸

重慶分公司,新征程啟航

為企業提供網站建設、域名注冊、服務器等服務

Rust中怎么實現閉包

今天就跟大家聊聊有關Rust中怎么實現閉包,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業的熱愛。我們立志把好的技術通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:域名與空間、虛擬空間、營銷軟件、網站建設、山東網站維護、網站推廣。

閉包的基本語法

閉包,又稱為lambda表達式,可以像函數一樣被調用,但具有使用外部環境中的變量的能力,其中外部環境是指閉包定義時所在的作用域。一個典型的閉包如下所示:

let x = 1;
let add = |a: i32| -> i32 {
	return a + x;
};
println!("{}", add(2)); // 輸出:3

可以看到,add是一個閉包,它有一個參數,用兩個|包圍,執行語句包含在{}中,閉包的參數和返回值類型的指定與普通函數的語法相同,但可以省略。若{}中只包含一條語句,{}也可以省略。也就是說,add可以簡寫為如下形式:

let add = |a| a + x;

在閉包add中,它可以使用外部環境中的變量x,這是和普通函數最大的不同。需要注意的是,對于普通函數fn來說,其無需前向聲明,但閉包需要先定義后使用,從這個角度講閉包更像是一個變量,而且它具有和變量同樣的“生命周期”。

下面我們來看一下閉包是如何使用外部環境中的變量的。

閉包的實現原理

對每一個閉包,編譯器會自動生成一個匿名struct類型,并通過分析閉包的內部邏輯來決定該結構體包括哪些數據以及數據該如何初始化,如果閉包中使用了外部環境變量,則結構體中會包括該變量。從這個層面講,閉包其實是一種語法糖。對于上面提到的閉包add,編譯器會將其自動轉化為如下形式(只是舉個例子,并非真正的編譯器處理閉包的方式):

struct Closure {
    x: i32,
}

impl Closure {
    fn call(&self, a: i32) -> i32 {
        self.x + a
    }
}
fn main() {
    let x = 1;
    let add = Closure { x: x };
    println!("{}", add.call(2)); // 輸出:3
}

可以看到,若想在閉包中使用一個外部環境中的變量,需要分兩步:第一步是構造相應的結構體并捕獲外部環境變量,就如let add = Closure { x: x };所示;第二步是構造結構體的成員函數并使用外部環境變量,就如fn call(&self, a: i32) -> i32所示。在這兩個步驟中,還需要考慮兩個問題:

  • 第一個問題關心的是閉包如何捕獲外部變量:結構體內部的成員應當使用什么類型呢,是T&T還是&mut T呢?

  • 第二個問題關心的是閉包如何使用外部變量:函數調用的self應當使用什么類型呢,是self&self還是&mut self呢?

如何捕獲外部變量

對于閉包如何捕獲外部變量,編譯器的原則是“按需捕獲”:在保證能編譯通過的情況下,結構體內部的成員優先選擇&T,其次是&mut T,最后選擇T。這個原則的核心就是“選擇對結構體外部影響最小的存儲類型”,具體來說就是:

  • 如果一個外部變量在閉包中只通過借用指針&使用,那么這個變量就可使用&捕獲;

  • 如果一個外部變量在閉包中通過可變借用指針&mut使用,那么這個變量就需要使用&mut捕獲;

  • 如果一個外部變量在閉包中通過所有權轉移方式使用過,那么這個變量就需要使用T捕獲;

看下面這個例子:

struct T(i32);

fn by_move(_: T) {}
fn by_ref(_: &T) {}
fn by_mut(_: &mut T) {}

fn main() {
    let x = T(1);
    let y = T(2);
    let mut z = T(3);

    let closure = || {
        by_move(x);
        by_ref(&y);
        by_mut(&mut z);
    };

    closure();
}

以上閉包分別以T&T還是&mut T的方式捕獲了外部的變量xy z,所以編譯器會自動生成類似于下面這樣的結構體:

struct Closure {
    x: T,
    y: &T,
    z: &mut T,
}

需要注意的是,如果承載閉包的變量不再是局部變量,而是被傳遞出了當前作用域,則該閉包必須選擇傳遞所有權的方式才能保證編譯通過,這時可以使用move關鍵字修飾閉包,強制將閉包中變量的捕獲全部使用所有權轉移的方式。例如:

fn make_adder(x: i32) -> Box i32> {
    Box::new(move |y| x + y)
}

可以看到,局部變量x被傳遞出了函數外,如果不加move關鍵字,編譯器會提示:

error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
如何使用外部變量

閉包使用外部變量的方式將影響相應的結構體成員函數的第一個參數self的類型,對于self&self&mut self三種類型,Rust提供了三個trait對其進行抽象。這三個trait是FnFnMutFnOnce,它們的定義如下:

pub trait FnOnce {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

pub trait FnMut: FnOnce {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

pub trait Fn: FnMut {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

可以看到,這三個trait的區別就在于self的類型不同:

  • FnOnce的調用參數為self,表示閉包通過轉移所有權的方式來使用外部環境中的變量,所以該閉包只能調用一次;

  • FnMut的調用參數是&mut self,表示閉包通過可變借用的方式來使用外部環境中的變量;

  • Fn的調用參數是&self,表示閉包通過不可變借用的方式來使用外部環境中的變量;

需要注意的是,Fn繼承自FnMutFnMut繼承自FnOnce,這意味著,如果要實現Fn,就必須實現FnMutFnOnce;如果要實現FnMut,就必須實現FnOnce。這里面蘊含的邏輯是,如果該閉包能夠以Fn方式調用,那么它也一定能以FnMutFnOnce方式調用。

看下面這個例子:

fn main() {
    let s = "hello".to_string();
    let use_by_ref = move || {
        println!("{}", s);
    };
    use_by_ref(); // Fn
    use_by_ref();

    let mut s = "hello".to_string();
    let mut use_by_mut = || {
        s.push_str(" world");
        println!("{}", s);
    };
    use_by_mut(); // FnMut
    use_by_mut();

    let s = "hello".to_string();
    let use_by_move = || {
        drop(s);
    };
    use_by_move(); // FnOnce
    // use_by_move(); // error[E0382]: use of moved value: `use_by_move`
}

可以看到,閉包use_by_ref是只讀方式使用了外部變量s,如果不使用move關鍵字,它的捕獲方式是&T,使用方式是Fn,這里為了演示,強制使用move關鍵字將捕獲方式改為T,但由于閉包中對變量s的使用仍然是只讀,所以使用方式仍然是Fn,而不是FnOnce;閉包use_by_mut是可變方式使用了外部變量s,它的捕獲方式是&mut T,使用方式是FnMut;閉包use_bu_move是所有權轉移方式使用了外部變量s,它的捕獲方式是T,使用方式是FnOnce,所以在第二次調用use_by_move時會報錯。

閉包的使用

閉包一定會實現FnFnMutFnOnce三種trait之一,所以可以將閉包作為函數參數和返回類型,但需要注意的是,不要忘記trait是一種DST類型,它的大小在編譯階段是不固定的,從而不能直接作為參數類型或者返回值類型,這也是Rust中的trait和其他語言中的接口的重大區別之一。請看下面的例子,在函數tset中不可以直接使用Bird作為類型,編譯器會報錯。

trait Bird {
    fn fly(&self);
}

struct Duck;
struct Swan;

impl Bird for Duck {
    fn fly(&self) {
        println!("duck duck");
    }
}

impl Bird for Swan {
    fn fly(&self) {
        println!("swan swan");
    }
}

// error[E0277]: the size for values of type `(dyn Bird + 'static)` cannot 
// be known at compilation time
fn test(arg: Bird) {}

難道我們在Rust中就不能擁有多態了嗎?那是不可能的,我們有兩種選擇:

  • 靜態分派:通過泛型的方式,為不同的泛型類型參數生成不同版本的函數,實現編譯期靜態分派。

fn test(arg: T) {
    arg.fly();
}
  • 動態分派,通過trait objetc的方式,將閉包裝箱進入堆內存中,函數傳遞的是一個胖指針,從實現運行期動態分派。

fn test(arg: Box) {
    arg.fly();
}

下面我們來具體介紹一下靜態分派和動態分派。

靜態分派

對于閉包,其泛型參數的寫法有一些特殊之處,如下面代碼所示:

fn call_with_closure(closure: F) -> i32
where
    F: Fn(i32) -> i32,
{
    closure(1)
}

其中泛型參數F的約束條件是F: Fn(i32) -> i32,這使得看起來和普通函數類型更相似從而更易閱讀。

使用泛型的方式在函數參數中可以正常使用,但卻無法將一個閉包作為函數值返回,因為Rust中只支持函數返回具體類型,而閉包是一個匿名類型,這使得編譯器無法自動推斷且程序員也無法手動指定。這時,可以使用impl trait語法糖,看下面的例子:

fn multiply(m: i32) -> impl Fn(i32) -> i32 {
    move |x| x * m
}

這里的impl Fn(i32) -> i32表示,這個返回類型,雖然我們不知道它的具體名字,但是知道它滿足Fn(i32) -> i32這個trait的約束。這個功能目前還不是特別穩定,建議不要激進使用,推薦使用下面介紹的動態分派的方式來解決返回值的問題。

動態分派

動態分派是通過指針的方式來實現多態,雖然trait是的DST,但指向trait的指針不是DST。如果我們把trait隱藏到指針的后面,那么就稱它是一個trait object,而trait object是可以作為參數和返回類型的。

指向trait的指針就是trait object。假如Bird是一個trait的名稱,那么dyn Bird就是一個DST動態大小類型,則&dyn BirdBox以及Rc等都是trait object。當指針指向trait的時候,它就變成一個胖指針了,比如Box可以理解為:

pub struct TraitObject {
    pub data: *mut(),
    pub vtable: *mut(),
}

它里面包含了兩個指針,第一個表示地址,第二個指向“虛函數表”,里面有我們需要調用的具體函數的地址。這和C++中的虛函數表內存布局有所不同。在C++中,如果一個類型里面有虛函數,則每一個這種類型的變量內部都包含一個指向虛函數表的地址。而在Rust中,對象本身不包含指向虛函數表的指針,這個指針是存在于trait object指針里面的,如果一個類型實現了多個trait,那么不同的trait object指向的虛函數表也不一樣。

需要注意的是,并不是所有的trait都可以構造trait object,trait objetc的構造是受到許多約束的。滿足以下條件的trait不能構造trait object:

  • 當trait有Self: Sized約束時

  • 當函數除了第一個參數外,有Self類型作為參數或返回類型時

  • 當函數的第一個參數不是Self

  • 當函數有泛型參數時

對于后三種情況,如果想把不滿足條件的函數剔除在外,可以為該函數加上Self: Sized約束,例如fn foo(&self) where Self: Sized

看完上述內容,你們對Rust中怎么實現閉包有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注創新互聯行業資訊頻道,感謝大家的支持。


當前文章:Rust中怎么實現閉包
新聞來源:http://www.xueling.net.cn/article/ihpshc.html

其他資訊

在線咨詢
服務熱線
服務熱線:028-86922220
TOP
主站蜘蛛池模板: 人与牲口性恔配视频免费 | 久久久久国产精品熟女影院浪 | a免费在线观看 | 国产一区日韩二区欧美三区 | 大学生无套流白浆视频大全 | 高清国产MV视频在线观看 | 午夜在线不卡 | 91久久精品国产91久久性色也 | 免费无遮挡无码视频在线观看 | 亚洲欧美www | 国产精品久久一区 | gay男生露j打飞j视频网站 | 国内揄拍国内精品少妇国语 | 国产大片B站免费观看推荐 97夜夜模夜夜爽夜夜喊 | 欧美大片1238| 麻豆av传媒蜜桃天美传媒 | 四虎最新影院 | 亚洲人成中文字幕在线观看 | 中国大陆一级毛片 | 国产不卡av在线播放 | 蜜桃视频最新网址 | 色拍拍在线精品视频 | 午夜嘿嘿嘿在线观看 | 国产免费久久精品99久久 | 国产日产一区二区三区久久久久久 | 日韩精品视频在线观看一区 | 老司机午夜精品99久久免费 | 内射后入在线观看一区 | 800av凹凸视频在线观看 | 中国亚洲女人69内射少妇 | 久久私拍视频 | 亚洲一本之道高清在线观看 | 亚洲丁香色 | 四虎影院在线观看av | 亚洲人和日本人jzz视频 | 欧美大黑BBB | 扒开两腿中间缝流白浆在线看 | 东北农村女人乱淫免费视频 | 亚洲少妇一级片 | 欧美成视频在线观看 | 日韩免费视频网站 |