跳至主要內容

04-函数式编程

黑静美原创...大约 4 分钟编程rust


分割线构成的框中内容摘编自:快速入门:Rust中有哪些你不得不了解的基础语法?open in new window(著作权归AI悦创所有)

Rust 中的函数和模块 (各种代码块)

最后我们来看 Rust 的函数、闭包和模块,它们用于封装和复用代码。

a. 函数

函数基本上是所有编程语言的标配,在 Rust 中也不例外,它是一种基本的代码复用方法。在 Rust 中使用 fn 关键字来定义一个函数。比如:

fn print_a_b(a: i32, b: char) {
    println!("The value of a b is: {a}{b}");
}

fn main() {
    print_a_b(5, 'h');
}

函数定义时的参数叫作形式参数(形参),函数调用时传入的参数值叫做实际参数(实参)。函数的调用要与函数的签名(函数名、参数个数、参数类型、参数顺序、返回类型)一致,也就是实参和形参要匹配。

函数对于几乎所有语言都非常重要,实际上各种编程语言在实现时,都是以函数作为基本单元来组织栈上的内存分配和回收的,这个基本的内存单元就是所谓的栈帧(frame),我们在下节课会讲到。

b. 闭包(Closure)

闭包是另一种风格的函数。它使用两个竖线符号 || 定义,而不是用 fn() 来定义。

Code1

// 标准的函数定义
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
// 闭包的定义,请注意形式对比
let add_one_v2 = |x: u32| -> u32 { x + 1 };
// 闭包的定义2,省略了类型标注
let add_one_v3 = |x|             { x + 1 };
// 闭包的定义3,花括号也省略了
let add_one_v4 = |x|               x + 1  ;

Code2

fn main() {
    let add_one = |x| x + 1; 
    let a_vec: Vec<u32> = vec![1,2,3,4,5];
    let vec2: Vec<_> = a_vec.iter().map(add_one).collect();
}

闭包与函数的一个显著不同就是,闭包可以捕获函数中的局部变量,而函数不行。比如,下面示例中的闭包 add_v2 捕获了 main 函数中的局部变量 a 来使用,但是函数 add_v1 就没有这个能力。

fn main() {
    let a = 10u32;             // 局部变量
    
    fn  add_v1   (x: u32) -> u32 { x + a }    // 定义一个内部函数
    let add_v2 = |x| x + a;   // 定义一个闭包
    
    let result1 = add_v1(20);  // 调用函数
    let result2 = add_v2(20);  // 调用闭包
    println!("{}", result2);
}

这样会编译出错,并提示错误。

error[E0434]: can't capture dynamic environment in a fn item
 --> src/main.rs:4:40
  |
4 |     fn  add_v1   (x: u32) -> u32 { x + a }    // 定义一个内部函数
  |                                        ^
  |
  = help: use the `|| { ... }` closure form instead

闭包之所以能够省略类型参数等信息,主要是其定义在某个函数体内部,从闭包的内容和上下文环境中能够分析出来那些类型信息。

c. 模块

我们不可能把所有代码都写在一个文件里面。代码量多了后,分成不同的文件模块书写是非常自然的事情。这个需求需要从编程语言层级去做一定的支持才行,Rust 也提供了相应的方案。

分文件和目录组织代码理解起来其实很简单,主要的知识点在于目录的组织结构上。比如下面示例:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden              // 子目录
    │   └── vegetables.rs
    ├── garden.rs           // 与子目录同名的.rs文件,表示这个模块的入口
    └── main.rs

第五行代码到第七行代码组成 garden 模块,在 garden.rs 中,使用 mod vegetables; 导入 vegetables 子模块。

main.rs 中,用同样的方式导入 garden 模块。

mod garden;

整个代码结构就这样一层一层地组织起来了。

另一种文件的组织形式来自 2015 版,也很常见,有很多人喜欢用。

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden          // 子目录
    │   └── mod.rs      // 子目录中有一个固定文件名 mod.rs,表示这个模块的入口
    │   └── vegetables.rs
    └── main.rs

同上,由第五行到第七行代码组成 garden 模块,在 main.rs 中导入它使用。

你可以在本地创建文件,来体会两种不同目录组织形式的区别。


上次编辑于:
贡献者: Heijingmei
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3