关于Rust中函数与闭包的使用介绍

163 篇文章 0 订阅

 

本文主要介绍一下Rust函数相关的内容,首先函数我们其实一直都在用,所以函数本身没什么可说的,我们的重点是与函数相关的闭包、高阶函数、发散函数,感兴趣的可以学习一下

闭包

Rust 的闭包由一个匿名函数加上外层的作用域组成,举个例子:

1

2

3

4

5

6

7

fn main() {

    let closure = |n: u32| -> u32 {

        n * 2

    };

    println!("n * 2 = {}", closure(12));

    // n * 2 = 24

}

闭包可以被保存在一个变量中,然后我们注意一下它的语法,参数定义、返回值定义都和普通函数一样,但闭包使用的是两个竖线。我们对比一下两者的区别:

1

2

3

4

5

6

7

8

9

// 普通函数定义

fn func1(a: u32, b: u32) -> String {

    // 函数体

}

/* 如果换成闭包的话,那么等价于

let func1 = |a: u32, b: u32| -> String {

    // 函数体

}

*/

所以两者在语法上没有什么本质的区别,但这个时候可能有人好奇了,我们能不能把闭包中的匿名函数换成普通函数呢?来试一下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

fn main() {

    let closure1 = |n: u32| -> u32 {

        n * 2

    };

    fn closure2(n: u32) -> u32 {

        n * 2

    }

    println!("n * 2 = {}", closure1(12));

    println!("n * 2 = {}", closure2(12));

    /*

    n * 2 = 24

    n * 2 = 24

    */

}

从表面上来看是可以的,但其实还存在问题,因为 closure2 只是一个在函数里定义的函数而已。而闭包除了要包含函数之外,还要包含函数所在的外层作用域,什么意思呢?我们举例说明:

你看到了什么?没错,在函数 closure2 内部无法使用外层作用域中的变量 a,因此它只是定义在 main 函数里的函数而已,而不是闭包,因为它不包含外层函数(main)的作用域。

而 Rust 提示我们使用 || { ... },那么 closure1 显然是闭包,因为它除了包含一个函数(匿名),还包含了外层作用域,我们将这个闭包赋值给了 closure1。

此外闭包还有一个重要的用途,就是在多线程编程时,可以将主线程的变量移动到子线程内部。

关于多线程后续会详细说,这里只是举个例子。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 导入线程模块

use std::thread;

fn main() {

    let s = String::from("hello world");

    // 必须在 || 的前面加上 move

    // 它的含义就是将值从主线程移动到子线程

    let closure1 = move || {

        println!("{}", s);

    };

    // 开启一个子线程

    thread::spawn(closure1).join();

    /*

    hello world

    */

}

打印是发生在主线程当中的,而不是子线程,以上就是闭包相关的内容。

高阶函数

了解完闭包之后,再来看看高阶函数,在数学和计算机中,高阶函数是至少满足下列一个条件的函数:

  • 接收一个或多个函数作为输入;
  • 输出一个函数;

在数学中它们也叫算子或者泛函,高阶函数是函数式编程中非常重要的一个概念。

先来看看如何定义一个接收函数作为参数的函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// calc 接收三个参数,返回一个 i32

// 参数一:接收两个 i32 返回一个 i32 的函数

// 参数二 和 参数三均是一个 i32

fn calc(method: fn(i32, i32) -> i32,

        a: i32, b: i32) -> i32 {

    method(a, b)

}

fn add(a: i32, b: i32) -> i32 {

    a + b

}

fn main() {

    println!("a + b = {}", calc(add, 12, 33));

    /*

    a + b = 45

    */

    // 也可以传递一个匿名函数,但它不能引用外层作用域的变量

    // 因为 calc 第一个参数接收的是函数,不是闭包

    let sub = |a: i32, b: i32| -> i32 {

        a - b

    };

    println!("a - b = {}", calc(sub, 12, 33));

    /*

    a - b = -21

    */

}

以函数作为参数,在类型声明中我们不需要写函数名以及参数名,只需要指明参数类型、数量和返回值类型即可。

然后再观察一下函数 calc 的定义,由于第一个参数 method 接收一个函数,所以它的定义特别的长,我们能不能简化一下呢?

1

2

3

4

5

6

7

// 相当于给类型起了一个别名

type Method = fn(i32, i32) -> i32;

fn calc(method: Method,

        a: i32, b: i32) -> i32 {

    method(a, b)

}

这种做法也是可以的。

看完了接收函数作为参数,再来看看如何将函数作为返回值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

type Method = fn(i32, i32) -> i32;

// 想要接收字符串的话

// 应该使用引用 &String 或切片 &str

// 当然我们前面说过,更推荐切片

fn calc(op: &str) -> Method {

    fn add(a: i32, b: i32) -> i32 {

        a + b

    }

     

    let sub = |a: i32, b: i32| -> i32 { a - b };

    // 使用 if else 也是可以的

    match op {

        "add" => add,

        "sub" => sub,

        // 内置的宏,会抛出一个错误,表示方法没有实现

        _ => unimplemented!(),

    }  // 注意:此处不可以加分号,因为要作为表达式返回

}

fn main() {

    let (a, b) = (11, 33);

    println!("a + b = {}", calc("add")(a, b));

    println!("a - b = {}", calc("sub")(a, b));

    /*

    a + b = 44

    a - b = -22

    */

}

以上就是高阶函数,还是很好理解的,和 Python 比较类似。你可以基于这个特性,实现一个装饰器,只是 Rust 里面没有 @ 这个语法糖罢了。这里我们简单地实现一下吧,加深一遍印象。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

enum Result {

    Text(String),

    Func(fn() -> String),

}

fn index() -> String {

    String::from("欢迎来到古明地觉的编程教室")

}

fn login_required(username: &str, password: &str) -> Result {

    if !(username == "satori" && password == "123") {

        return Result::Text(String::from("请先登录"));

    } else {

        return Result::Func(index);

    }

}

fn main() {

    let res1 = login_required("xxx", "yyy");

    let res2 = login_required("satori", "123");

    // 如果后续还要使用 res1 和 res2,那么就使用引用

    // 也就是 [&res1, &res2]

    // 但这里我们不用了,所以是 [res1, res2],此时会转移所有权

    for item in [res1, res2] {

        match item {

            Result::Text(error) => println!("{}", error),

            Result::Func(index) => println!("{}", index()),

        }

    }

    /*

    请先登录

    欢迎来到古明地觉的编程教室

    */

}

是不是很有趣呢?这里再次看到了枚举类型的威力,我们有可能返回字符串,也有可能返回函数,那么应该怎么办呢?很简单,将它们放到枚举里面即可,这样它们都是枚举类型。至于到底是哪一个成员,再基于 match 分别处理即可。

还记得 match 吗?match 可以有任意多个分支,每一个分支都应该返回相同的类型,并且只有一个分支会执行成功,然后该分支的返回值会作为整个 match 表达式的返回值。

发散函数

最后再来看看发散函数,这个概念在其它语言里面应该很少听到。在 Rust 里面,发散函数永远不会返回,它的返回值被标记为 !,表示这是一个空类型。

1

2

3

4

5

6

7

8

9

10

11

// 发散函数的返回值类型是一个感叹号

// 它表示这个函数执行时会报错

fn foo() -> ! {

    panic!("这个函数执行时会报错")

}

fn main() {

    // 调用发散函数时,可以将其结果赋值给任意类型的变量

    let res1: u32 = foo();

    let res2: f64 = foo();

}

所以这个发散函数没啥卵用,你在实际开发中估计一辈子也用不上,因为它在执行的时候会 panic 掉。所以这段代码编译的时候是没有问题的,但执行时会触发 panic。既然执行时会报错,那么当然可以赋值给任意类型的变量。

因此当返回值类型为 ! 时,我们需要通过 panic 宏让函数在执行的过程中报错。但要注意的是,发散函数和不指定返回值的函数是不一样的,举个例子:

1

2

3

4

5

6

7

8

9

10

11

12

// 发散函数的返回值类型是一个感叹号

// 它表示这个函数执行时会报错

fn foo() -> ! {

    panic!("这个函数执行时会报错");

}

// 不指定返回值,默认返回 ()

// 所以以下等价于 fn bar() -> () {}

// 但很明显 bar 函数是有返回值的,会返回空元组

fn bar() {

}

总的来说发散函数没啥卵用,在工作中也不建议使用,只要知道有这么个东西就行。

到此这篇关于深入了解Rust中函数与闭包的使用的文章就介绍到这了

 

来源:微点阅读  https://www.weidianyuedu.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值