rust ABC (三)函数式编程

rust ABC (三)函数式编程

rust 作为一种函数式语言,在语言设计上借鉴了函数式语言的思想,处处都可以体现。比如我们会好奇,为什么很多函数名会带上 ! 符号,以 print! 打印函数来说明,因为打印对控制台,也就是对外部环境造成了影响,每次调用函数都会改变外部的值,因此他不是一个纯函数。同样这种函数名写法在早期的函数式语言(比如今的主流语言比如java、go还要早)很常见,算是一种新瓶装旧酒。同样这里会介绍map-reduce、Option、Result、模式匹配的常用使用方式。

map-reduce

类似于 java 的 stream ,rust 同样实现了 map-reduce

    let arr = vec![1,2,3,4,5,6,7,8,9,10];

    let res: Vec<_> = arr.iter().map(|i| i + 1)
        .filter(|i| i  / 2 == 0)
        .collect();
    println!("{:?}", res); // [2,4,6,8,10]

其中 rust 的 _ 是一种语法糖,因为这里可以通过类型推断出i32类型,因此可以使用语法糖省略,在模式匹配中, _ 语法糖也是很常见的。

Option

Option就是选项,目的是为了防止空值。

Option<T> 有两个变量:

在模式匹配中使用,如果除数为0,就返回None

    fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
        if divisor == 0 {
            // 失败表示成 `None` 取值
            None
        } else {
            // 结果 Result 被包装到 `Some` 取值中
            Some(dividend / divisor)
        }
    }

    let dividend = 12;
    let divisor = 0;
    match checked_division(dividend, divisor) {
        None => println!("{} / {} failed!", dividend, divisor),
        Some(quotient) => {
            println!("{} / {} = {}", dividend, divisor, quotient)
        },
    }

Result

Result就是结果,用于错误异常的处理,在运行期提前抛出错误保证程序在正常运行时抛出错误而不执行后面的程序,而不是panic!。分为左值和右值,左值保存错误的信息,右值保存最后的结果。

Result<T, E> 类型拥有两个取值:

    enum MathError {
        DivisionByZero,
        NegativeLogarithm,
        NegativeSquareRoot,
    }

    type MathResult = Result<f64, MathError>;

    fn div(x: f64, y: f64) -> MathResult {
        if y == 0.0 {
            // 此操作将会失败,那么(与其让程序崩溃)不如把失败的原因包装在
            // `Err` 中并返回
            Err(MathError::DivisionByZero)
        } else {
            // 此操作是有效的,返回包装在 `Ok` 中的结果
            Ok(x / y)
        }
    }

Option 和 Result 组合使用

一般来说,Option保证没有空值,Result保证程序流程中正常抛出错误,而不再后面的程序中崩溃,他们有着各自的作用,一般的签名方式是Result<Option<T>,E>

?语法糖

同样Option的值的获取经常会使用链式调用,但是不知道其中哪一个值会空值,因此提供了? 语法糖来链式调用。适用于 Option 和 Result 。但是这个目的是为了简化嵌套模式匹配,只能在函数的return 中使用

//Option
#[derive(Clone, Copy)]
struct C {
    z: Option<i32>,
}
#[derive(Clone, Copy)]
struct B {
    y: Option<C>,
}
#[derive(Clone, Copy)]
struct A {
    x: Option<B>,
}

fn work_phone_area_code(a : &A) -> Option<i32> {
    a.x?.y?.z
}

//Result
use std::num::ParseIntError;

fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
    let first_number = first_number_str.parse::<i32>()?;
    let second_number = second_number_str.parse::<i32>()?;

    Ok(first_number * second_number)
}

fn print(result: Result<i32, ParseIntError>) {
    match result {
        Ok(n)  => println!("n is {}", n),
        Err(e) => println!("Error: {}", e),
    }
}


当然,Result 中的 ! 语法糖和 try! 宏是等价的

模式匹配

rust 提供了强大的模式匹配功能,并有一些语法糖

_ 语法糖,比如这里没有使用 Err(e) 里面的值,那么就可以省略掉

fn print(result: Result<i32, String>) {
    match result {
        Ok(n)  => println!("n is {}", n),
        _ => println!("Error: something went wrong"),
    }
}

ref 关键字

ref 就是引用,以下两句是等价的

let c = 'Q';

// 赋值语句中左边的 `ref` 关键字等价于右边的 `&` 符号。
let ref ref_c1 = c;
let ref_c2 = &c;

一般只有一个作用,是为了解决模式匹配中语法欠缺的问题。在模式匹配中作用是在模式匹配时创建对值的引用(借用),而不是移动或复制该值

let name = String::from("Alice");

// 错误:match 会尝试 move name,之后 name 就失效了
// match name {
//     s => println!("{s}"),
// }
// println!("{name}"); // use of moved value

// 正确:ref 让 s 成为 &String,name 的所有权不变
match name {
    ref s => println!("{s}"),
}
println!("{name}"); // name 仍然可用