rust入坑之旅-闭包

Scroll Down

Rust 中的闭包(closure),也叫做 lambda 表达式 是这样式的|val| val + x。Java中的lambda 表达式 是这样的 x -> !x.equals("") 是一类输入和输出可以自动推导,而捕获周围作用域中变量必须声明的函数。

写个🌰

基于闭包和函数分别实现自增。

fn main() {
    // 下面这行是使用函数的实现
    fn  function            (i: i32) -> i32 { i + 1 }

    // 闭包是匿名的,这里我们将它们绑定到闭包类型。
    // 类型标注和函数的一样,不过类型标注和使用 `{}` 来围住函数体都是可选的。
    // 匿名函数被赋值给本地变量。
    let closure_annotated = |i: i32| -> i32 { i + 1 };
    let closure_inferred  = |i     |          i + 1  ;
    // 闭包表达式产生的类型就是 “闭包类型”,不属于引用类型,而且确实无法对上面两个 `closure_xxx` 变量解引用。
    let i = 1;
    // 调用函数和闭包。
    println!("function: {}", function(i));
    println!("closure_annotated: {}", closure_annotated(i));
    println!("closure_inferred: {}", closure_inferred(i));
    // 没有参数的闭包,返回一个 `i32` 类型。
    // 返回类型是自动推导的。
    let one = || 1;
    println!("closure returning one: {}", one());
	
}

捕获变量

闭包本质上很灵活,能做功能要求的事情,使闭包在没有类型标注的情况下运行。可以灵活地适配实例,对变量既可移动(move),又可借用(borrow)

  • 借用(borrow)
    use std::mem;
    let color = String::from("green");
    // 这个闭包打印 `color`。它会立即借用(通过引用,`&`)`color` 并将该借用和
    // 闭包本身存储到 `print` 变量中。`color` 会一直保持被借用状态直到
    // `print` 离开作用域。
    //
    // `println!` 只需传引用就能使用,而这个闭包捕获的也是变量的引用,因此无需
    // 进一步处理就可以使用 `println!`。
    let print = || println!("`color`: {}", color);
    // 使用借用来调用闭包 `color`。
    print();
    // `color` 可再次被不可变借用,因为闭包只持有一个指向 `color` 的不可变引用。
    let _reborrow = &color;
    print();
    let mut count = 0;
    // 这个闭包使 `count` 值增加。要做到这点,它需要得到 `&mut count` 或者
    // `count` 本身,但 `&mut count` 的要求没那么严格,所以我们采取这种方式。
    // 该闭包立即借用 `count`。
    //
    // `inc` 前面需要加上 `mut`,因为闭包里存储着一个 `&mut` 变量。调用闭包时,
    // 该变量的变化就意味着闭包内部发生了变化。因此闭包需要是可变的。
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // 使用可变借用调用闭包
    inc();

    // 因为之后调用闭包,所以仍然可变借用 `count`
    // 试图重新借用将导致错误
    // let _reborrow = &count;
    // ^ 试一试:将此行注释去掉。
    inc();

    // 闭包不再借用 `&mut count`,因此可以正确地重新借用
    let _count_reborrowed = &mut count;

    // 不可复制类型(non-copy type)。
    let movable = Box::new(3);

    // `mem::drop` 要求 `T` 类型本身,所以闭包将会捕获变量的值。这种情况下,
    // 可复制类型将会复制给闭包,从而原始值不受影响。不可复制类型必须移动
    // (move)到闭包中,因而 `movable` 变量在这里立即移动到了闭包中。
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume` 消耗了该变量,所以该闭包只能调用一次。
    consume();
    //consume();
    // ^ 试一试:将此行注释去掉。
  • 移动(move)
fn main() {
    // `Vec` 在语义上是不可复制的。
    let haystack = vec![1, 2, 3];

    let contains = move |needle| haystack.contains(needle);

    println!("{}", contains(&1));
    println!("{}", contains(&4));

    //println!("There're {} elements in vec", haystack.len());
    // ^ 取消上面一行的注释将导致编译时错误,因为借用检查不允许在变量被移动走
    // 之后继续使用它。

    // 在闭包的签名中删除 `move` 会导致闭包以不可变方式借用 `haystack`,因此之后
    // `haystack` 仍然可用,取消上面的注释也不会导致错误。
}

闭包参数

尽管入参和范湖可以自动推到,但是当这个闭包作为一个函数的参数是,Rust必须让其在编译器得到一个确切的类型;类型模糊否则无法编译成功;此处涉及FnOnce 换成 FnFnMut这是三个trait。

// 该函数将闭包作为参数并调用它。
fn apply<F>(f: F) where
    // 闭包没有输入值和返回值。
    F: FnOnce() {
    //  可以`FnOnce` 换成 `Fn` 或 `FnMut`。

    f();
}
  • Fn:表示捕获方式为通过引用(&T)的闭包
  • FnMut:表示捕获方式为通过可变引用(&mut T)的闭包
  • FnOnce:表示捕获方式为通过值(T)的闭包
// 该函数将闭包作为参数并调用它。
fn apply<T>(f: T)
where
    // 闭包没有输入值和返回值。
    T: FnOnce(),
{
    // ^ 试一试:将 `FnOnce` 换成 `Fn` 或 `FnMut`。

    f();
}

// 输入闭包,返回一个 `i32` 整型的函数。
fn apply_to_3<F>(f: F) -> i32
where
    // 闭包处理一个 `i32` 整型并返回一个 `i32` 整型。
    F: FnOnce(i32) -> i32,
{
    f(3)
}
pub fn test_clousure() {
    use std::mem;
    let greeting = "hello";
    // 不可复制的类型。
    // `to_owned` 从借用的数据创建有所有权的数据。
    let mut farewell = "goodbye".to_owned();
    // 捕获 2 个变量:通过引用捕获 `greeting`,通过值捕获 `farewell`。
    let diary = || {
        println!("I said {}.", greeting);
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");
        mem::drop(farewell);
    };

    // 以闭包作为参数,调用函数 `apply`。
    apply(diary);

    // 闭包 `double` 满足 `apply_to_3` 的 trait 约束。
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}


mod closure;
fn main() {
 closure::test_clousure();
}

如果用一个类型说明为 FnOnce 的闭包作为参数。这说明闭包可能采取 &T,&mut T 或 T 中的一种捕获方式,但编译器最终是根据所捕获变量在闭包里的使用情况决定捕获方式。

  • 需要使用Fn
let diary = || {
        println!("I said {}.", greeting);
    };
  • 使用FnMut
 let diary = || {
       // 获取了 mut farewell 所以在apply函数也需要都f进行修改 需要用mut修饰
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");
    };
  • 使用FnOnce
    let diary = || {
        println!("I said {}.", greeting);
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");
//手动调用 drop 又要求闭包通过值获取 `farewell`。
        mem::drop(farewell);
    };

作为返回值

闭包作为输入参数是可能的,所以返回闭包作为返回值,然而返回闭包类型会有问题,因为目前 Rust 只支持返回具体(非泛型)的类型。按照定义,匿名的闭包的类型是未知的,所以只有使用impl(FnOnce FnFnMut这是三个trait)才能返回一个闭包。除此之外,还必须使用 move 关键字,它表明所有的捕获都是通过值进行的。这是必须的,因为在函数退出时,任何通过引用的捕获都被丢弃,在闭包中留下无效的引用。

fn create_fn() -> impl Fn() {
    let text = "Fn".to_owned();

    move || println!("This is a: {}", text)
}

fn create_fnmut() -> impl FnMut() {
    let text = "FnMut".to_owned();

    move || println!("This is a: {}", text)
}

fn create_fnonce() -> impl FnOnce() {
    let text = "FnOnce".to_owned();
    
    move || println!("This is a: {}", text)
}

fn main() {
    let fn_plain = create_fn();
    let mut fn_mut = create_fnmut();
    let fn_once = create_fnonce();

    fn_plain();
    fn_mut();
    fn_once();
}


std标准库

src/iter/traits/iterator.rs目录下的trait
Iterator::any 是一个函数,若传给它一个迭代器(iterator),当其中任一元素满足谓词(predicate)时它将返回 true,否则返回 false

pub trait Iterator {
    /// The type of the elements being iterated over.
    #[stable(feature = "rust1", since = "1.0.0")]
    type Item;
    #[inline]
    #[stable(feature = "rust1", since = "1.0.0")]
//表明函数的调用者可以被借用和修改,但不会被消耗。
    fn any<F>(&mut self, f: F) -> bool
    where
        Self: Sized,
//FnMut` 表示被捕获的变量最多只能被修改,而不能被消耗。
//`Self::Item` 指明了被捕获变量的类型
//`FnMut` 就表示闭包只能通过引用捕获变量。把类型为 `T` 的变量作为闭包, 的参数不代表闭包会拿走它的值,也可能是拿走它的引用。
        F: FnMut(Self::Item) -> bool,
    {
        #[inline]
        fn check<T>(mut f: impl FnMut(T) -> bool) -> impl FnMut((), T) -> ControlFlow<()> {
            move |(), x| {
                if f(x) { ControlFlow::BREAK } else { ControlFlow::CONTINUE }
            }
        }

        self.try_fold((), check(f)) == ControlFlow::BREAK
    }
 // `find` 接受 `&mut self` 参数,表明函数的调用者可以被借用和修改,
    // 但不会被消耗。
    fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where
        // `FnMut` 表示被捕获的变量最多只能被修改,而不能被消耗。
        // `&Self::Item` 指明了被捕获变量的类型(译注:是对迭代器元素的引用类型)
        P: FnMut(&Self::Item) -> bool {}
}
测试any
fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];

    // 对 vec 的 `iter()` 举出 `&i32`。(通过用 `&x` 匹配)把它解构成 `i32`。
    // 译注:注意 `any` 方法会自动地把 `vec.iter()` 举出的迭代器的元素一个个地
    // 传给闭包。因此闭包接收到的参数是 `&i32` 类型的。
    println!("2 in vec1: {}", vec1.iter()     .any(|&x| x == 2));
    // 对 vec 的 `into_iter()` 举出 `i32` 类型。无需解构。
    println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));

    let array1 = [1, 2, 3];
    let array2 = [4, 5, 6];

    // 对数组的 `iter()` 举出 `&i32`。
    println!("2 in array1: {}", array1.iter()     .any(|&x| x == 2));
    // 对数组的 `into_iter()` 通常举出 `&i32`。
    println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));
}

测试find
fn test_find() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];

    // 对 vec1 的 `iter()` 举出 `&i32` 类型。
    let mut iter = vec1.iter();
    // 对 vec2 的 `into_iter()` 举出 `i32` 类型。
    let mut into_iter = vec2.into_iter();

    // 对迭代器举出的元素的引用是 `&&i32` 类型。解构成 `i32` 类型。
    // 译注:注意 `find` 方法会把迭代器元素的引用传给闭包。迭代器元素自身
    // 是 `&i32` 类型,所以传给闭包的是 `&&i32` 类型。
    println!("Find 2 in vec1: {:?}", iter.find(|&&x| x == 2));
    // 对迭代器举出的元素的引用是 `&i32` 类型。解构成 `i32` 类型。
    println!("Find 2 in vec2: {:?}", into_iter.find(|&x| x == 2));

    let array1 = [1, 2, 3];
    let array2 = [4, 5, 6];

    // 对数组的 `iter()` 举出 `&i32`。
    println!("Find 2 in array1: {:?}", array1.iter().find(|&&x| x == 2));
    // 对数组的 `into_iter()` 通常举出 `&i32``。
    println!(
        "Find 2 in array2: {:?}",
        array2.into_iter().find(|&x| x == 2)
    );
}