閉包是一個可捕獲周圍環境的可執行代碼片段,基本的幾個定義方式如下:

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

| |內部的是捕獲的周圍的變量,默認捕獲的是不可變借用,先給出一個實際代碼片段:

use std::thread;
use std::time::Duration;

fn main() {
    let foo = |num| {  // 定義一個閉包,捕獲一個變量
        thread::sleep(Duration::from_millis(num));
        println!("sleep for {} milliseconds", num);
    };

    let num = 200;
    foo(num);
}

注意:閉包一般不必顯式聲明變量的類型,但是閉包只能推斷一種類型的數據,再次出現其它類型的數據時會報錯。 給出代碼説明:

fn main() {
    let foo = |x| x;

    foo(1.1);
    foo("foo".to_string());  // 這裏會報錯,第一次推斷的時候,就已經明確這是f32的類型看了
}

如果我們想讓閉包在第一次調用時就保存好結果,之後返回第一次計算的結果,可以使用緩存的機制,下面代碼給出一般的緩存機制。但是,這個機制存在一個問題,如果第一次有了計算結果了,那麼再次傳入新的值,返回的也是第一次計算的結果。

struct Cache<T>
    where T: Fn(u32) -> u32  // 注意這裏閉包聲明的方式
{
    calculation: T,
    value: Option<u32>
}

impl<T> Cache<T>
    where T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cache<T> {
        Cache {
            calculation,
            value: None
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            },
        }
    }
}

fn main() {
    let mut res = Cache::new(|num| {
        num * num
    });

    let v1 = res.value(2);
    let v2 = res.value(10);  // 這仍然會返回之前的結果
    println!("v1 = {}, v2 = {}", v1, v2);  // v1 = 4, v2 = 4
}

如果想要根據不同的值計算結果,可以利用HashMap等的思路。

閉包三種捕獲方式,附帶3種聲明方式:

  • FnOnce 消費從周圍作用域捕獲的變量,閉包周圍的作用域被稱為其 環境,environment。為了消費捕獲到的變量,閉包必須獲取其所有權並在定義閉包時將其移動進閉包。其名稱的 Once 部分代表了閉包不能多次獲取相同變量的所有權的事實,所以它只能被調用一次。
  • FnMut 獲取可變的借用值所以可以改變其環境
  • Fn 從其環境獲取不可變的借用值