0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Rust语言中闭包的应用场景

科技绿洲 来源:TinyZ 作者:TinyZ 2023-09-20 11:25 次阅读

Rust语言的闭包是一种可以捕获外部变量并在需要时执行的匿名函数。闭包在Rust中是一等公民,它们可以像其他变量一样传递、存储和使用。闭包可以捕获其定义范围内的变量,并在必要时访问它们。这使得闭包在许多场景下非常有用,例如迭代器、异步编程和并发编程。

闭包与函数的区别在于,闭包可以捕获它所定义的环境中的变量。这意味着,当闭包中使用变量时,它可以访问该变量的值。在Rust中,闭包被设计为可以自动推断变量的类型,因此可以方便地使用。

Rust闭包概念和python中Lambda表达式,Java的Lambda表达式很类似,可以帮助理解和应用。

闭包的应用场景

闭包在Rust语言中被广泛应于许多场景。例如,在多线程编程中,闭包可以用来定义线程任务。在Web开发中,闭包可以用来定义路由处理函数。在数据处理领域,闭包可以用来定义数据转换和过滤函数等等。 下面,我们以Animal为例,演示如何使用闭包实现一些常见的数据处理和转换操作。

use std::collections::HashMap;

#[derive(Debug)]
struct Animal {
    name: String,
    species: String,
    age: i32,
}


impl Animal {
    fn new(name: &str, species: &str, age: i32) - > Self {
        Animal {
            name: name.to_owned(),
            species: species.to_owned(),
            age,
        }
    }
}

impl Display for Animal {
    fn fmt(&self, f: &mut Formatter) - > Result {
        write!(f, "Animal info name {}, species:{}, age:{}", self.name, self.species, self.age)
    }
}

fn main() {
    let animals = vec![
        Animal::new("Tom", "Cat", 2),
        Animal::new("Jerry", "Mouse", 1),
        Animal::new("Spike", "Dog", 3),
    ];

    // 计算所有动物的平均年龄
    let total_age = animals.iter().map(|a| a.age).sum::< i32 >();
    let average_age = total_age as f32 / animals.len() as f32;
    println!("Average age: {:.2}", average_age);

    // 统计每个物种的数量
    let mut species_count = HashMap::new();
    for animal in &animals {
        let count = species_count.entry(animal.species.clone()).or_insert(0);
        *count += 1;
    }
    println!("Species count: {:?}", species_count);

    // 找出所有年龄大于2岁的动物
    let old_animals: Vec< _ > = animals.iter().filter(|a| a.age > 2).collect();
    println!("Old animals: {:?}", old_animals);

    // 将所有动物的名字转换成大写
    let upper_names: Vec< _ > = animals.iter().map(|a| a.name.to_uppercase()).collect(); 
    println!("Upper case names {:?}", upper_names);
}
//    输出结果:
// Average age: 2.00
// Species count: {"Dog": 1, "Cat": 1, "Mouse": 1}
// Old animals: [Animal { name: "Spike", species: "Dog", age: 3 }]
// Upper case names ["TOM", "JERRY", "SPIKE"]

在上面的代码中,我们定义了一个Animal结构体,其中包含了动物的名称、物种和年龄信息。我们使用Vec类型来存储所有动物的信息。接下来,我们使用包对这些动物进行了一些常见的数据处理和转换操作。

首先,我们计算了所有动物的平均年龄。我们使用iter()方法对Vec进行迭代,并使用map()方法将每个动物的年龄提取出来。然后,我们使用sum()方法将所有的年龄相加,并将其转换为i32类型。最后,我们将总年龄除以动物数量,得到平均年龄。

接下来,我们统计了每个物种的数量。我们使用HashMap类型来存储物种和数量的映射关系。我们使用entry方法获取每个物种的数量,如果该物种不存在,则插入一个新的映射关系,并将数量初始化为0。最后,我们使用filter()方法和闭包找出了所有年龄大于2岁的动物。我们使用map()方法和闭包将所有动物的名字转换成大写,然后使用collect()方法将它们收集到一个新的Vec中。最后,我们使用map()方法和闭包将所有动物的名字转换成大写。

在上面的示例中,我们可以看到闭包的强大之处。使用闭包,我们可以轻松地对数据进行转换和处理,而不必定义大量的函数。此外,闭包还可以捕获外部环境中的变量,使得代码更加灵活和可读。

闭包的语法

包的语法形式如下:

|arg1, arg2, ...| body

其中,arg1arg2...表示闭包参数body表示闭包函数体。闭包可以有多个参数,也可以没有参数。如果闭包没有参数,则可以省略||之间的内容。

无参数闭包示例:

fn main() {
    let greet = || println!("Hello, World!");
    greet();
}
//    输出结果:
//    Hello, World!

闭包的函数体可以是任意有效的Rust代码,包括表达式、语句和控制流结构等。在闭包中,我们可以使用外部作用域中的变量。这些变量被称为闭包的自由变量,因为它们不是闭包参数,但是在闭包中被引用了。

闭包的自由变量示例如下:

fn main() {
    let x = 3;
    let y = 5;
    //  在这里y,就是闭包的自由变量  
    let add = |a, b| a + b + y;
    
    println!("add_once_fn: {}", add(x,y));
}
//    输出结果:
//    13

在上面的示例中,我们定义了一个闭包add,没用指定参数的具体类型,这里是使用到了Rust语言的闭包类型推导特性,编译器会在调用的地方进行类型推导。这里值得注意的几点小技巧定义的闭包必须要有使用,否则编译器缺少类型推导的上下文。当编译器推导出一种类型后,它就会一直使用该类型,和泛型有本质的区别。

//    1. 将上面例子的pringln!注释掉, 相当于add闭包没用任何引用,编译报错
error[E0282]: type annotations needed
  -- > src/main.rs:13:16
   |
13 |     let add = |a, b| a + b + y;
   |                ^
   |
help: consider giving this closure parameter an explicit type
   |
13 |     let add = |a: /* Type */, b| a + b + y;
   |                 ++++++++++++
//    2. 新增打印 println!("add_once_fn: {}", add(0.5,0.6));

error[E0308]: arguments to this function are incorrect
  -- > src/main.rs:16:33
   |
16 |     println!("add_once_fn: {}", add(0.5,0.6));
   |                                 ^^^ --- --- expected integer, found floating-point number
   |                                     |
   |                                     expected integer, found floating-point number
   |
note: closure defined here
  -- > src/main.rs:13:15
   |
13 |     let add = |a, b| a + b + y;
   |               ^^^^^^

闭包可以使用三种方式之一来捕获自由变量:

  • move关键字:将自由变量移动到闭包内部,使得闭包拥有自由变量的所有权。这意味着,一旦自由变量被移动,部作用域将无法再次使用它。
  • &引用:使用引用来访问自由变量。这意味着,外部作用域仍然拥有自由变量的所有权,并且可以在闭包之后继续使用它。
  • &mut可变引用:使用可变引用来访问自由变量。这意味着,外部作用域仍然拥有自由变量的所有权,并且可以在闭包之后继续使用它。但是,只有一个可变引用可以存在于任意给定的时间。如果闭包中有多个可变引用,编译器将无法通过。

下面是具有不同捕获方式的闭包示例:

fn main() {
    let x = 10;
    let y = 20;
    
    // 使用move关键字捕获自由变量
    let add = move |a:i32, b:i32| a + b + x;
    
    // 使用引用捕获自由变量
    let sub = |a:i32, b:i32| a - b - y;
    
    // 使用可变引用捕获自由变量
    let mut z = 30;
    let mut mul = |a:i32, b:i32| {
        z += 1;
        a * b * z
    };
    println!("add {}", add(x, y))
    println!("sub {}", sub(x, y))
    println!("mul {}", mul(x, y))
}
//    输出结果:
// add 40
// sub -30
// mul 6200

在上面的示例中,我们定义了三个闭包:addsubmul

  • add使用move关键字捕获了自由变量x,因此它拥有x的所有权。
  • sub使用引用捕获了自由变量y,因此它只能访问y的值,而不能修改它。
  • mul使用可变引用捕获了自由变量z,因此它可以修改z的值。在这种情况下,我们需要使用mut关键字来声明可变引用。

闭包的类型

在Rust语言中,闭包是一种特殊的类型,被称为FnFnMutFnOnce。这些类型用于区分闭包的捕获方式和参数类型。

  • Fn:表示闭包只是借用了自由变量,不会修改它们的值。这意味着,闭包可以在不拥有自由变量所有权的情况下访问它们。
  • FnMut:表示闭包拥有自由变量的可变引用,并且可能会修改它们的值。这意味着,闭包必须拥有自由变量的所有权,并且只能存在一个可变引用。
  • FnOnce:表示闭包拥有自由变量的所有权,并且只能被调用一次。这意味着,闭包必须拥有自由变量的所有权,并且只能在调用之后使用它们。

在闭包类型之间进行转换是非常简单的。只需要在闭包的参数列表中添加相应的trait限定,即可将闭包转换为特定的类型。例如,如果我们有一个Fn类型的闭包,但是需要将它转换为FnMut类型,只需要在参数列表中添加mut关键字,如下所示:

fn main() {
    let x = 3;
    let y = 5;
    let add = |a:i32, b:i32| a + b;
    let mut add_mut = |a:i32, b:i32| {
        let result = a + b;
        println!("Result: {}", result);
        result
    };
    
    let add_fn: fn(i32, i32) - > i32 = add;
    let add_mut_fn: fn(i32, i32) - > i32 = add_mut;
    let add_once_fn: fn(i32, i32) - > i32 = |a:i32, b:i32| a + b + 10;
    println!("add_fn: {}", add_fn(x,y));
    println!("add_mut_fn: {}", add_mut_fn(x,y));
    println!("add_once_fn: {}", add_once_fn(x,y));
}
//    输出结果:
// add_fn: 8
// Result: 8
// add_mut_fn: 8
// add_once_fn: 18

在上面的示例中,我们定义了三个闭包:addadd_mutadd_onceaddadd_mut都是Fn类型的闭包,但是add_mut使用了可变引用,因此它也是FnMut类型闭包。我们使用fn关键字将闭包转换为函数类型,并指定参数和返回值的类型。在这种情况下,我们使用i32作为参数和返回值的类型。

闭包的应用与实践

闭包在Rust语言中广泛应用于函数式编程、迭代器和多线程等领域。在函数式编程中,闭包常常用于实现高阶函数,如map()filter()reduce()等。这些函数可以接受一个闭包作为参数,然后对集合中的每个元素进行转换、过滤和归约等操作。

以下是一个使用闭包实现map()filter()函数的示例:

fn map< T, F >(source: Vec< T >, mut f: F) - > Vec >
where
    F:Mut(T) - > T,
{
    let mut result = Vec::new();
    for item in source {
        result.push(f(item));
    }
    result
}

fn filter< T, F >(source: Vec< T >, mut f: F) - > Vec< T >
where
    F: FnMut(&T) - > bool,
{
    let mut result = Vec::new();
    for item in source {
        if f(&item) {
            result.push(item);
        }
    }
    result
}

在上面的示例中,我们定义了map()filter()函数,它们接受一个闭包作为参数,并对集合中的每个元素进行转换和过滤操作。map()函数将集合中的每个元素传递给闭包进行转换,并将转换后的结果收集到一个新的Vec中。filter()函数将集合中的每个元素传递给闭包进行过滤,并将通过过滤的元素收集到一个新的Vec中。

以下是一个使用闭包实现多线程的示例:

use std::thread;

fn main() {
    let mut handles = Vec::new();
    for i in 0..10 {
        let handle = thread::spawn(move || {
            println!("Thread {}: Hello, world!", i);
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
}
//    输出结果:
// Thread 1: Hello, world!
// Thread 7: Hello, world!
// Thread 8: Hello, world!
// Thread 9: Hello, world!
// Thread 6: Hello, world!
// Thread 5: Hello, world!
// Thread 4: Hello, world!
// Thread 3: Hello, world!
// Thread 2: Hello, world!
// Thread 0: Hello, world!

在上面的示例中,我们使用thread::spawn()函数创建了10个新线程,并使用闭包将每个线程的编号传递给它们。在闭包中,我们使用move关键字将i移动到闭包内部,以便在创建线程之后,i的所有权被转移 给了闭包。然后,我们将每个线程的句柄存储在一个Vec中,并使用join()函数等待每个线程完成。

总结

Rust语言中的闭包是一种非常强大的特性,可以用于实现高阶函数、函数式编程、迭代器和多线程等领域。闭包具有捕获自由变量的能力,并且可以在闭包后继续使用它们。在Rust语言中,闭包是一种特殊的类型,被称为FnFnMutOnce,用于区闭包的捕获方式和参数类型。闭包可以通过实现这些trait来进行类型转换。

尽管闭包在Rust语言中非常强大和灵活,但是使用它们时需要谨慎。闭包的捕获方式和参数类型可能会导致所有权和可变性的问题,尤其是在多线程环境中。因此,我们应该在使用闭包时仔细思考,并遵循Rust语言的所有权和可变性规则。

总之,闭包是一种非常有用的特性,可以帮助我们编写更加灵活和高效的代码。如果您还没有使用过闭包,请尝试在您的项目中使用它们,并体验闭包带来的便利和效率。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    12

    文章

    3863

    浏览量

    84676
  • 编程
    +关注

    关注

    88

    文章

    3441

    浏览量

    92415
  • 数据处理
    +关注

    关注

    0

    文章

    511

    浏览量

    28248
  • rust语言
    +关注

    关注

    0

    文章

    58

    浏览量

    2958
  • 闭包
    +关注

    关注

    0

    文章

    4

    浏览量

    2037
收藏 人收藏

    评论

    相关推荐

    聊聊Rust与C语言交互的具体步骤

    rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊
    发表于 07-06 11:15 1027次阅读

    C语言中预定义宏的用法和使用场景

    在C语言中,预定义宏是由编译器提供的一组特殊标识符,可以在程序中直接使用,无需进行额外的定义。
    发表于 08-16 16:12 262次阅读

    如何在Rust中高效地操作文件

    Rust语言是一种系统级、高性能的编程语言,其设计目标是确保安全和并发性。 Rust语言以C和C++为基础,但是对于安全性和并发性做出了很大
    的头像 发表于 09-19 11:51 1464次阅读

    SQLx在Rust语言中的基础用法和进阶用法

    SQLx是一个Rust语言的异步SQL执行库,它支持多种数据库,包括MySQL、PostgreSQL、SQLite等。本教程将以MySQL数据库为例,介绍SQLx在Rust语言中的基础
    的头像 发表于 09-19 14:32 3084次阅读

    Rust语言中错误处理的机制

    Rust语言中,错误处理是一项非常重要的任务。由于Rust语言采用静态类型检查,在编译时就能发现很多潜在的错误,这使得程序员能够更加自信和高效地开发程序。然而,即使我们在编译时尽可能
    的头像 发表于 09-19 14:54 785次阅读

    基于Rust语言Hash特征的基础用法和进阶用法

    Rust语言是一种系统级编程语言,具有高性能、安全、并发等特点,是近年来备受关注的新兴编程语言。在Rust
    的头像 发表于 09-19 16:02 741次阅读

    Rust语言中的反射机制

    Rust语言的反射机制指的是在程序运行时获取类型信息、变量信息等的能力。Rust语言中的反射机制主要通过 Any 实现。 std::any::Any trait Any trait是所
    的头像 发表于 09-19 16:11 1143次阅读

    Rust语言如何与 InfluxDB 集成

    Rust 是一种系统级编程语言,具有高性能和内存安全性。InfluxDB 是一个开源的时间序列数据库,用于存储、查询和可视化大规模数据集。Rust 语言可以与 InfluxDB 集成,
    的头像 发表于 09-30 16:45 709次阅读

    基于Rust语言中的生命周期

    Rust是一门系统级编程语言具备高效、安和并发等特,而生命周期是这门语言中比较重要的概念之一。在这篇教程中,我们会了解什么是命周期、为什么需要生命周期、如何使用生命周期,同时我们依然会使用老朋友
    的头像 发表于 09-19 17:03 604次阅读

    Rust 语言中的 RwLock内部实现原理

    Rust是一种系统级编程语言,它带有严格的内存管理、并发和安全性规则,因此很受广大程序员的青睐。RwLock(读写锁)是 Rust 中常用的线程同步机制之一,本文将详细介绍 Rust
    的头像 发表于 09-20 11:23 484次阅读

    D语言,Go语言,Rust语言优势分析

    编者按】本文是D语言来呢后创始人、架构师Andrei Alexandrescu在问答Quora回答在取代C语言的道路上,D、Go和Rust谁的前途最光明?为什么?的答案,从自己的角度谈及了D、Go
    发表于 10-13 11:11 0次下载

    C语言中的关键字

    C语言中的入门教程
    发表于 10-14 16:24 3次下载

    详细介绍go语言中的闭包的实现

    什么是闭包? 什么场景下会用闭包 ? 本文对 go 语言中的闭包做了详细介绍。 闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。 Go中的闭包 闭包是函数式语言中的概念
    的头像 发表于 10-20 16:18 1684次阅读

    带你了解go语言中的闭包

      【 导读】什么是闭包? 什么场景下会用闭包 ? 本文对 go 语言中的闭包做了详细介绍。 闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。 Go中的闭包 闭包是函数式语言中
    的头像 发表于 11-02 15:27 2193次阅读

    tokio模块channel中的使用场景和优缺点

    以让不同的线程之间通过发送和接收消息来传递数据,从而实现线程之间的协作和同步。 在 Rust 语言中,tokio 模块的 channel 组件提供了
    的头像 发表于 09-19 15:54 400次阅读