rust 基础之闭包/迭代器和智能指针

闭包

闭包有点类似于匿名函数,写法如下

let expensive_closure = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};
expensive_closure(intensity)

类型推断和标注

  • 闭包不要求像 fn 函数那样在参数和返回值上注明类型

  • 闭包通常短小,关联小范围的context(编译器能可靠的推断参数和返回值的类型)

  • 闭包为每个参数和返回值推断一个具体类型(也可以具体标注)

  • 注意:闭包的定义最终只会为参数/返回值推断出唯一的具体类型

    // 类型推断
    let example_closure = |x| x;
    let s = example_closure(String::from("hello"));
    let n = example_closure(5); //string类型已经锁定进闭包,因而报错
    // 手动标注类型
    let expensive_closure = |num: u32| -> u32 {
        // ...
    };
    

当在多处调用函数时,可以利用struct封装闭包并缓存结果,达到需要结果才调用函数的效果,避免多次执行函数(记忆化或延迟计算)

  • struct定义需要知道所有字段的类型(明确闭包类型)
  • 每个闭包实例都是唯一的匿名类型(即使两个闭包签名完全一样)
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

所有的闭包和函数都实现了 trait FnFnMutFnOnce 中的一个。例如以下代码

  • 只有为T类型实现fn traitCacher才会实现new方法
  • 由于闭包实现了Fn,因此在初始化Cacher时传入一个函数或者闭包即可实现new方法
// src/lib.rs
impl<T> Cacher<T>
    where T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            },
        }
    }
}
// src/main.rs

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });
    // snip   
}

闭包会捕获上下文中的变量(函数则不行),但是会产生额外的内存开销。捕获的方式有三种:

  • FnOnce(所有闭包都实现) ,会取得所有权因而只能调用一次

  • FnMut ,获取可变的借用值

  • Fn, 获取不可变的借用值

fn main() {
    let x = 4;
    let equal_to_x = |z| z == x;
    // n equal_to_x(z: i32) -> bool { z == x } // 报错
    let y = 4;
    assert!(equal_to_x(y));
}

move关键词,可以强制闭包获取其使用的环境值的所有权

let x = vec![1,2,3];
let equal_to_x = move |z| z == x; // 强制闭包获取其使用的环境值的所有权
println!("can't use x here: {:?}", x); 

迭代器

迭代器是 惰性的lazy),便于对一个序列的项进行某些处理

let v1_iter = vec![1, 2, 3].iter();
for val in v1_iter {
    println!("Got: {}", val);
}

迭代器实现了Iterator trait

  • 实现iterator trait需要定义item类型,用于next方法的返回类型
pub trait Iterator {
    type Item; //关联类型
    fn next(&mut self) -> Option<Self::Item>;
    // 此处省略了方法的默认实现
}

获取迭代器

  • iter ,方法生成一个不可变引用的迭代器
  • into_iter,获取所有权并返回拥有所有权的迭代器
  • iter_mut ,迭代可变引用

自定义迭代器的实现只需要 Iterator trait,并实现next方法

impl Iterator for Counter {
    // 关联类型
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

消耗迭代器的方法(默认实现方法),例如

  • next(消耗型适配器)
  • sum(耗尽型迭代器)

产生其他迭代器的方法

  • 迭代器适配器,将迭代器转换为不同种类的迭代器
  • 因此可以通过链式调用使用多个适配器进行复杂操作,可读性较高
// io示例程序的迭代器链式执行
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents.lines()
        .filter(|line| line.contains(query)) // 编列迭代器元素返回bool类型,如果为true则包含在filter产生的迭代器中
        .collect()
}

智能指针

引用是最简单的指针,而智能指针

  • 智能指针 拥有 它们指向的数据。
  • 智能指针通常使用结构体实现,区别于常规结构体的一点在于实现了 DerefDrop trait
  • 拥有额外的元数据和功能

常用的智能指针

  • Box,用于在heap内存上分配值
  • Rc,一个引用计数类型,其数据可以有多个所有者
  • RefRefMut,通过 RefCell 访问( RefCell 是一个在运行时而不是在编译时执行借用规则的类型)

Box类型

使用Box来获得确定大小的递归类型

BoX是一个指针,rust知道它需要多少空间(指针的大小是恒定的),因为:

  • 指针的大小不会基于它指向的数据的大小变化而变化
  • BoX只提供了“间接”存储和heap内存分配的功能,没有其它额外功能,没有性能开销,适用于需要“间接”存储的场景,例如Cons List
  • 实现了Deref traitDrop trait

Deref trait

实现Deref Trait可以自定义解引用运算符*的行为,感觉就是运算符重载

  • 通过实现Deref,智能指针可以像常规引用一样来处理
let x = 5;
// let y = &x;
let y = Box::new(x);
prinln!("{}",*y) // *(y.deref())

实现Deref trait的要求

  • 实现一个deref方法,借用self
  • 返回一个指向内部数据的引用
use std::ops::Deref;

struct MyBox<T>(T); //元组结构体,实际是个具名元组

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}

隐式解引用转化

函数和方法的隐式解引用转化(Deref Coercion

  • 假设T类型实现了Deref traitDeref coercion可以把T的引用转化为T经过Deref之后生成的引用

  • 当某类型的引用传递给函数或方法时,但是它的类型于定义的参数类型不匹配,Deref Coercion会自动发生,将其转化为所需的参数类型

  • 在编译时完成,灭有额外的性能开销

对于以上Mybox的示例程序

fn hello(name: &str){
	println!("{}",name);
}
fn main(){
	let m = MyBox::new(String::from("hello");
    // 此时会发生解引用转化
    // rest 会不断尝试调用deref将&MYBox引用转化为&str
	hello(&m);
    // hello(&(*m)[..]); // 如果不适用隐式解引用转换,会出现冗杂的写法
}

解引用与可变性

类似于Deref trait,使用DerefMut trait 用于重载可变引用的*运算符

以下三种情况时会进行解引用强制转换:

  • T: Deref :从 &T&U
  • T: DerefMut :从 &mut T&mut U
  • T: Deref :从 &mut T&U

Drop trait

实现Drop trait自定义当值离开作用域时发生的动作

  • 任何类型都可以实现Drop trait

  • 只要求实现drop方法即可,是预导入的

  • 不允许手动调用实现的drop方法,但是可以调用标准库的std::mem::drop函数(预导入)提前触发drop

Rc引用计数智能指针

一个值有多个所有者,为了支持多重所有权

  • 维护引用计数(referece couting),并追踪所有到值的引用
  • 当引用数量为0,则表示值可以清理

使用场景为,需要再heap上分配数据,并被程序的多个部分读取,但在编译时无法确定那个部分最后使用完这些数据。

  • Rc只能用于单线程场景
  • 不在预导入模块中
  • Rc::clone(&a)函数,增加引用计数
  • Rc::string_count(&a),获得引用计数
  • Rc::weak_count(&a)
  • Rc引用是不可变的
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

RefCell内部可变模式

内部可变模式interior mutability pattern使不可变类型暴露出可修改其内部值的api

RefCell代表了持有数据的唯一所有权,对比Box

Box RefCell
编译阶段强制代码遵守借用规则 只会在运行时检查借用规则
否则出现错误 否则触发panic
尽早暴露问题,没有运行时开销,默认行为和通常的最佳选择 问题暴露延后,性能损失,特定环境的内存安全问题(不可变环境中修改自身数据)

由于编译器的保守型,可能拒绝一些实际上遵守借用规则的代码(编译器无法理解),如果开发者能够保证借用规则满足,则可以使用RefCell

  • 只能用于单线程场景
  • 即使RefCell不可变,仍旧能够修改其中的值

选择的依据

rust 基础之闭包/迭代器和智能指针_第1张图片

RefCell会记录当前存在的活跃RefRefMut智能指针

  • 调用borrow,不可变借用计数+1
  • 任何一个Ref的值离开作用域释放时,不可变借用计数-1
  • 调用borrow_mut,可变借用计数+1
  • 任何一个RefMut的值离开作用域释放时,可变借用计数-1

维护如下的借用检查规则

  • 任意时间,只允许拥有多个不可变借用或一个可变借用

你可能感兴趣的:(编程语言,rust,开发语言,后端)