Bootstrap

Rust学习(六):函数式编程

Rust学习(六):函数式编程

我们在前一篇博客中已经介绍了如何通过trait和impl实现Rust的面向对象编程,但是Rust本身实际上并不提倡通过类来解决问题。Rust推崇的是函数式编程,强调将函数作为参数值或者其他函数的返回值,将函数赋值给变量之后在继续执行。其中最重要的两个概念就是:闭包函数和迭代器。

1、闭包函数:

闭包函数是一种可以保存变量或作为参数传递给其他函数使用的匿名函数,和C++中的lambda表达式非常相似,闭包可以在一处创建,然后在不同的上下文中使用,同时闭包函数可以捕获调用者的作用域中的值:

//定义闭包函数:
|parameters| {
    cody_body;
    return_value
}

闭包函数还可以进一步简化,Rust可以自动推动闭包函数的参数类型和返回值类型,因此,闭包可以没有参数和返回值:

let is_even = |x| {
    x % 2 == 0
};

let num = 10;
println!("{num} is even {}", is_even(num));

如上面这个案例所示:使用闭包可以只将其赋值给变量,然后就像调用函数一样调用它即可(doge),同时闭包也可以使用外部变量:

let val = 2;
let add_val = |x| {x + val};
let num = 2;
let res = add_val(num);
println!("{num} + {val} = {res}");

如果大家和记得前面对所有权的描述,,那就一定会好奇:这里的闭包函数获取的到底是外部变量的所有权还是外部变量的引用?Rust为此设计了三个trait:

  • FnOnce:使用这个trait的闭包函数会获取外部变量的所有权。
  • FnMut:获取外部变量的可变引用。
  • Fn:获取外部变量的借用值。

如果需要强制将外部变量所有权转移到闭包内,那么可以使用move关键字:

let val = 2;
let add_val = move |x| {x+val};

2、迭代器:

迭代器会把集合中所有元素按照顺序一个一个传递给处理逻辑,允许对一个序列进行某些处理,并且会遍历这个序列中的每一项以决定何时结束。我们之前使用的for循环就是一个迭代器,迭代器默认实现了Iterator trait——iter(),用于返回i迭代器和next(),用于返回迭代器的下一项,他们是迭代器的核心功能!

根据迭代器迭代时是否可以修改数据,iter()方法有三个版本:

  • iter():返回只读可重入迭代器,元素类型:&T
  • iter_mut():返回可修改可重入迭代器,元素类型:&mut T
  • into_iter():返回只读不可重入迭代器,元素类型:T

可重入是指:迭代后原始数据还能使用,不可重入则代表迭代器消费了原始数据(这里可以借用python中的pop()方法,取出元素,并删除)

let nums = vec![1, 2, 3, 4, 5, 6];

//使用iter()方法:
for num in nums.iter() {
    println!("num:{num}");
}
println!("{:?}", nums);  //可以使用原数据nums

//使用iter_mut()方法:
for num in nums.iter_mut() {
    *num += 1;
}
println!("{:?}", nums);  //可以使用原数据nums

//使用into_iter()方法:
for num in nums.into_iter() {
    println!("num:{num}");
}
//for num in nums.iter() {println!("num:{num}");}  错误!nums已经被消费

消费是迭代器中的一种有趣且特殊的操作,sum, next, nth, fold等都是消费者,他们会对迭代器进行操作,得到最终值:

fn main() {
	let nums = vec![1, 2, 3, 4, 5, 6];
    let nums_iter = nums.iter();
    let total = nums_iter.sum::<i32>();
    
    let new_nums : Vec<i32> = (0..100).filter(|&n| n % 2 == 0).collect();
    
    println!("{:?}", new_nums);
    
    // 求小于等于1000的能被3或5整除的所有整数之和:
    let sum = (1..1000).filter(|n| n % 3 == 0 || n % 5 == 0).sum::<u32>();
    println!("{sum}");
}

实际上,除了函数式编程之外,还有命令式编程、声明式编程等多种编程范式。我们平时所用的面向对象编程(C++和python)和结构式编程(C)都属于命令式编程,命令式编程的主要思想是一步一步的精确的给出计算机运行程序的指令,而声明式编程(SQL)则以数据结构的形式表达变成逻辑,主要思想是告诉计算机应该做什么,而不是具体怎么做的(其实有点夸大了),函数式编程的思想和声明式编程类似,它更进一步是面向数学的抽象,旨在将计算描述为一种数学表达式求值,寻求一种输入输出的映射关系。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;