rust学习-闭包

这篇具有很好参考价值的文章主要介绍了rust学习-闭包。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

模拟健康推荐算法,为前端提供高强度/低强度的训练app

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

fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    intensity
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(
        simulated_user_specified_value,
        simulated_random_number
    );
}

fn generate_workout(intensity: u32, random_number: u32) {
    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            simulated_expensive_calculation(intensity)
        );
        // 这里想直接拿结果,但是依旧需要重新计算
        // 解决办法在后文
        println!(
            "Next, do {} situps!",
            simulated_expensive_calculation(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                simulated_expensive_calculation(intensity)
            );
        }
    }
}

simulated_expensive_calculation 是个算法模块维护的内容,且未来变化较大,所以代码中期待对其只使用一次。

用闭包重构

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

fn generate_workout(intensity: u32, random_number: u32) {
    // 闭包的定义以一对竖线(|)开始,在竖线中指定闭包的参数
    // 如果有多于一个参数,可以使用逗号分隔,比如 |param1, param2|
    // 如果闭包体只有一行则大括号是可以省略
    // let 语句意味着 expensive_closure 包含一个匿名函数的 定义
    // 不是调用匿名函数的 返回值
    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        // 闭包体的最后一行没有分号(正如函数体一样)
        // 所以闭包体(num)最后一行的值作为调用闭包时的返回值
        num
    };

    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            expensive_closure(intensity)
        );
        println!(
            "Next, do {} situps!",
            expensive_closure(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_closure(intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(
        simulated_user_specified_value,
        simulated_random_number
    );
}

闭包类型推断和标注

闭包不要求像 fn 函数那样在参数和返回值上注明类型
函数需要类型标注是因为是暴露给用户的显式接口的一部分
如果一定要类型标注,也不是不可以

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  ;

闭包定义会为每个参数和返回值推断一个具体类型

let example_closure = |x| x;
let s = example_closure(String::from("hello"));
// 编译器推断 x 和此闭包返回值的类型为 String
// 这些类型被锁定进闭包 example_closure 中
// 如果尝试对同一闭包使用不同类型则会得到类型错误
// 如下编译则报错
// let n = example_closure(5);

使用带有泛型和 Fn trait 的闭包

解决在一个上下文中,闭包被重复计算的问题
方法一:
创建一个存放闭包和调用闭包结果的变量(不同的上下文可能变量会很多)
方法二:
创建一个存放闭包和调用闭包结果的结构体
该结构体只会在需要结果时执行闭包,并会缓存结果值,即缓存

lazy evaluation (惰性求值)

// 定义一个 Cacher 结构体来在 calculation 中存放闭包
// 在 value 中存放 Option 值
// 结构体中存储闭包类型:闭包有一个 u32 的参数并返回一个 u32
// 这样所指定的 trait bound 就是 Fn(u32) -> u32
// 所有的闭包都实现了 trait Fn、FnMut 或 FnOnce 中的一个,这里使用 trait Fn
//
// 注意:函数也都实现了这三个 Fn trait
// 如果不需要捕获环境中的值,则可以使用实现了 Fn trait 的函数而不是闭包
//
// 在执行闭包之前,value 将是 None
// 如果使用 Cacher 的代码请求闭包的结果,这时会执行闭包并将结果储存在 value 字段的 Some 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 Some 成员中的结果
use std::thread;
use std::time::Duration;

struct Cacher<T>
    where T: Fn(u32) -> u32
{
    // Cacher 结构体的字段是私有的,代表着仅由Cacher的方法管理它们
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
    where T: Fn(u32) -> u32
{
    // Cacher::new 获取一个泛型参数 T,和结构体有着相同的trait bound
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            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); // 将结果存储到Some中
                v
            },
        }
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            expensive_result.value(intensity)
        );
        println!(
            "Next, do {} situps!",
            expensive_result.value(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.value(intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(
        simulated_user_specified_value,
        simulated_random_number
    );
}

该cacher的缺点
(1)Cacher 实例假设对于 value 方法的任何 arg 参数值总是会返回相同值

#[test]
fn call_with_different_values() {
    let mut c = Cacher::new(|a| a);

    let v1 = c.value(1); // 此时缓存中的值是1
    let v2 = c.value(2); // 此时缓存中的值还是1

    assert_eq!(v2, 2);
}

解决办法:Cacher 存放哈希 map 而不是单独一个值
哈希 map 的 key 将是传递进来的 arg 值
哈希 map 的 value 则是对应 key 调用闭包的结果值
value 函数会在哈希 map 中寻找 arg,如果找到的话就返回其对应的值。
如果不存在,Cacher 会调用闭包并将结果值保存在哈希 map 对应 arg 值的位置。

(2)不够泛型
它的应用被限制为只接受获取一个 u32 值并返回一个 u32 值的闭包
如果需要能够缓存一个获取字符串 slice 并返回 usize 值的闭包的结果呢?

闭包捕获其环境

闭包还有另一个函数所没有的功能:
他们可以捕获其环境并访问其被定义的作用域的变量

当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。
这会使用内存并产生额外的开销

fn main() {
    let x = 4;
    // 闭包中直接使用变量x,因为它与 equal_to_x 定义于相同的作用域
    // 函数则不行
    let equal_to_x = |z| z == x;
    let y = 4;
    assert!(equal_to_x(y));
}

捕获环境的三种方式:

闭包周围的作用域被称为其环境,environment
对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用
创建一个闭包时,Rust 根据其如何使用环境中变量来推断如何引用环境。

FnOnce

为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。
其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权,所以它只能被调用一次。

FnMut

获取可变的借用值,可以改变其环境

Fn

从其环境获取不可变的借用值文章来源地址https://www.toymoban.com/news/detail-575142.html

fn main() {
    // 整型可以被拷贝而不是移动,所以这里采用vec
    let x = vec![1, 2, 3];
    
    // 强制闭包获取其使用的环境值的所有权
    // 闭包获取了 x 的所有权
    let equal_to_x = move |z| z == x;
    // 这里x无法打印,编译报错
    // println!("can't use x here: {:?}", x);

    let y = vec![1, 2, 3];

    assert!(equal_to_x(y));
}

到了这里,关于rust学习-闭包的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust- 闭包

    A closure in Rust is an anonymous function you can save in a variable or pass as an argument to another function. You can create the closure using a lightweight syntax and access variables from the scope in which it’s defined. Here’s an example of a closure that increases a number by one: In this example, plus_one is a closure that takes one argument x a

    2024年02月14日
    浏览(35)
  • rust 闭包函数

    函数有自己的类型,可以像使用基础类型一样使用函数,包括将函数保存在变量中、保存在 vec 中、声明在结构体成员字段中。闭包函数也是函数,也有自己的类型定义。不过,函数实际上是指针类型,在 rust 所有权中属于借用的关系。 我们声明一个 Vec 对象集,并使用闭包

    2024年02月15日
    浏览(46)
  • 研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)

    测试我不打算很详细的写,大家知道如何使用其实就差不多了 一般来说我们在lib中编写测试 这样我们构建了一个test的lib 在这个工程里面你看到应该是有个lib.rs没有main.rs的 这里并不是声明一个mod,而是一个测试区域,在区域中可以写很多的测试方法 我们通过 #[cfg(test)] 宏来

    2023年04月22日
    浏览(52)
  • rust 自动化测试、迭代器与闭包、智能指针、无畏并发

    编写测试可以让我们的代码在后续迭代过程中不出现功能性缺陷问题;理解迭代器、闭包的函数式编程特性; BoxT 智能指针在堆上存储数据, RcT 智能指针开启多所有权模式等;理解并发,如何安全的使用线程,共享数据。 编写测试以方便我们在后续的迭代过程中,不会改坏

    2024年02月16日
    浏览(40)
  • Rust软件外包开发语言的特点

    Rust 是一种系统级编程语言,强调性能、安全性和并发性的编程语言,适用于广泛的应用领域,特别是那些需要高度可靠性和高性能的场景。下面和大家分享 Rust 语言的一些主要特点以及适用的场合,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公

    2024年02月12日
    浏览(51)
  • Rust语言从入门到入坑——(2)Rust在windows上搭建开发环境

    开始搭建一个适合在windows上运行的Rust环境。 Rust支持的程序语言很多:可详见官网介绍 本文章主要是在windowns下搭建开发环境 首先,需要安装最新版的 Rust 编译工具和 Visual Studio Code。 Rust 编译工具:https://www.rust-lang.org/zh-CN/tools/install Visual Studio Code:https://code.visualstudio.com

    2024年02月09日
    浏览(52)
  • Rust 程序设计语言学习——基础语法

    Rust 语言是一种高效、可靠的通用高级语言。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。 Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软

    2024年01月22日
    浏览(52)
  • Go语言入门8(匿名函数 闭包)

    ​顾名思义,就是没有名字的函数。。。 匿名函数的两种执行方法 将匿名函数赋给一个变量 定义后立即执行匿名函数 ​闭包是由函数和与其相关的引用环境组合而成的实体,个人认为就是把一个函数以及函数所用到的参数用另外一个函数包起来,保证函数多次运行的时候,

    2023年04月20日
    浏览(63)
  • Go语言入门9(匿名函数 闭包)

    ​顾名思义,就是没有名字的函数。。。 匿名函数的两种执行方法 将匿名函数赋给一个变量 定义后立即执行匿名函数 ​闭包是由函数和与其相关的引用环境组合而成的实体,个人认为就是把一个函数以及函数所用到的参数用另外一个函数包起来,保证函数多次运行的时候,

    2023年04月24日
    浏览(53)
  • 【从零开始的rust web开发之路 一】axum学习使用

    第一章 axum学习使用 本职java开发,兼架构设计。空闲时间学习了rust,目前还不熟练掌握。想着用urst开发个web服务,正好熟悉一下rust语言开发。 目前rust 语言web开发相关的框架已经有很多,但还是和java,go语言比不了。 这个系列想完整走一遍web开发,后续有时间就出orm,还

    2024年02月12日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包