Unique's Blog

Rust基础(二)

2022-05-30 · 2186字 · 10 min read
🏷️  Rust

Hello Rust.

[TOC]

Rust 快速入门系列的第二篇。

闭包|迭代器

闭包

闭包:可以捕获其所在环境的匿名函数。(C++类似lambda,可以从其定义的作用域捕获值)

闭包可以保存为变量,作为函数参数,或者函数返回值。

fn main(){
	let closure = |x| x;    // let closure = |x: String| -> String {x};
	let s = closure(String::from("hello"));
	// let n = closure(5); // error 上一句已经推断出closure中的具体类型
}
  • 闭包不要求标注参数和返回值的类型

  • 闭包的定义,最终只会被推断出唯一的具体类型(或者显示标注类型)

每个闭包实例都对应一个唯一匿名类型,并且闭包都实现了以下Fn Trait之一:

  • Fn 不可变借用环境值

  • FnMut 可变借用环境值

  • FnOnce 取得所有权

利用闭包实现 Cacher 缓存器,实现延迟计算/惰性求值
use std::collections;
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: collections::HashMap<u32, u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: collections::HashMap::new(),
        }
    }
    fn value(&mut self, arg: u32) -> u32 {
        match self.value.get(&arg) {
            Some(v) => *v,
            None => {
                let v = (self.calculation)(arg);
                self.value.insert(arg, v);
                v
            }
        }
    }
}

【闭包还不支持泛型】

捕获变量

普通函数不能捕获上下文变量,而闭包可以。结合上述3种Fn Trait,Rust可以根据环境值的使用情况推断出具体的闭包类型:

  • 默认只捕获值的不可变引用,实现 Fn (不可变借用,只能读)

  • 获取值的可变引用,实现 FnMut(可变借用,可以修改值)

  • 获取值的所有权,实现 FnOnce,只能调用一次(因为获取了所有权)

💡

注意:3种Trait存在层级关系;实现了Fn的闭包都实现了FnMut,而实现了FnMut的闭包都实现了FnOnce,因此所有的闭包都实现了FnOnce.

使用move关键字:

可以在定义闭包的参数列表前使用 move,强制闭包获取值的所有权。

将闭包传递给新线程以移动数据时,非常有用。

let x = vec![1, 2, 3];
let equal_to_x = move |z: i32| z == x[0];
// println!("{:#?}", x); // error [E0382]: use of moved value: `x`

对于基本类型以及引用类型,没有所有权的变量不影响。

迭代器

Rust 迭代器是惰性迭代器,用于遍历:

let v = vec![1, 2, 3];
// for val in v 
// 调用 into_iter() moved
for val:&i32 in v.iter() { // borrow
    println!("{}", val);
}

实现:

迭代器都实现了trait Iterator,需要实现next方法;该 trait 也提供了一些默认实现方法。

pub trait Iterator{
    type Item;
    fn next(&mut self)->Option<Self::Item>;
    // default methods elided
}

产生迭代器的迭代方法:

  • iter() 创建迭代器,用于迭代元素的不可变引用

  • iter_mut() 创建迭代器,用于迭代元素的可变引用

  • **into_iter()**获取所有权,创建迭代器用于迭代元素本身;

迭代器适配器

Iterator trait 提供了迭代器适配器方法,用来将迭代器转换为不同种类的迭代器。

例如 map, filter

let v = vec![1, 2, 3];
let iter = v.iter().map(|x: &i32| x * 2);
let iter2 = v.iter().map(|x| x * 2).filter(|x| x > &3);

// 使用消耗型适配器方法(获取迭代器所有权,并会使用next方法)
// collect 收集元素到集合类型
let v2:Vec[i32] = iter.collect();

使用 zip 操作两个迭代器,转换为新的迭代器(元组类型)

遇到其中一个值是 None,迭代会停止。

示例
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

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
        }
    }
}
let sum: u32 = Counter::new().take(5)
    .zip(Counter::new().skip(1).take(4))
    .map(|(a,b)| a*b)
    .filter(|x| x%3==0)
    .sum();
assert_eq!(18, sum);

智能指针

Rust 中的引用(使用&)就是指针类型,借用指向的值,是最常见的指针类型。(切片也是引用类型,会有长度信息)

💡

智能指针的行为类似指针,具有额外的元数据和功能;并且一般会拥有指向 数据的所有权

例如String | Vec<T>,拥有一片内存区域以及元数据和相关功能。

智能指针,语义上应该实现Deref trait 和 Drop trait

Deref Trait: 实现 deref() 方法
//允许智能指针 自定义解引用运算符 * 的行为
Drop  Trait: 实现 drop() 方法
// 自定义当指针指针实例离开作用域时运行的代码

即实现了 Deref Trait (返回引用类型),使得实现该 trait 类型的变量可以使用*var操作

*var
// <==>
*(var.deref())
  • 不允许显示调用 drop 方法,但是可以使用std::mem::drop函数;

解引用转换

Deref tait 将&T ⇒ &U并且类型 T 隐式实现了类型 U 的所有(不可变)方法。(隐式)解引用强制转换 deref coercion,实现了 Deref trait 的类型可以将其引用链式调用 deref 转换为其他类型的引用。

例如,String 实现了 Deref,可以自动将&String转换为&str

示例代码 MyBox
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
    }
}

fn main(){
    let m = MyBox::new(String::from("Rust"));
    hello("hello Rust");
    hello(&m);
    hello(&String::from("Rust"));
    hello(&(*m)[..])
}
  • 允许使用DerefMut trait来重载可变引用的*运算符
💡

Deref trait可以将可变引用可以转换为另一种类型不可变引用;DerefMut trait将可变引用可以转换为另一种类型的可变引用。

常见类型

Box

最简单的智能指针,在堆内存上分配值,栈空间有指向数据的指针。其拥有数据所有权。

Rc

多重所有权引用计数智能指针Rc(Reference counting),使得一份数据被多个所有者持有,引用计数为0才清理数据。通过不可变引用只提供对数据的不可变访问。

场景:

  • 堆上数据被多个部分读取(不可变借用,但是编译器无法确定哪个部分最后使用完数据(否则将最后使用的部分设置为值的所有者即可);

  • 只能用于单线程

Rc::new()用来生成对应类型的引用计数智能指针,使用Rc::clone(&rc)来获取相同值的引用计数智能指针(引用计数+1)

enum 和 Rc 示例
use crate::List::{Cons, Nil};
use std::rc::Rc;

// List 中元素存活时间至少比 List 的生命周期相同
#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

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));
    // print strong_count
    println!("strong_count: {}", Rc::strong_count(&a)); // 获取强引用计数
    println!("a = {:#?}", b);
    println!("b = {:#?}", c);
}

RefCell

用于实现内部数据的可变性,允许你在只持有不可变引用的前提下对数据进行修改。

  • 导入std::cell::RefCell

  • 代表持有数据的唯一所有权,其可变性和借用规则是运行时期检查。

  • 用于单线程环境

智能指针比较:

使用场景:需要在不可变环境中修改自身的数据,将数据使用RefCell<T>包装,可以在运行期获取可变引用来修改数据。用于实现内部可变性

  • 获取内部值的不可变引用borrow()方法,返回Ref<T>其实现了 Deref

  • 获取内部值的可变引用borrow_mut()方法,返回RefMut<T>其实现了 Deref

use std::cell::RefCell
struct MockMes{
	sent_messages: RefCell<Vec<String>>,
}

impl MockMes{
	fn send(&self, message: &str){
		self.sent_messages.borrow_mut().push(String::from(message));
	}
}

Ref<T> |RefMut<T>(RefCell方法)访问数据不可变引用和可变引用。

RefCell 会记录这两种指针个数,来维护运行时的借用检查规则,给定时间只允许拥有多个不可变借用或者一个可变借用。

通常会将 Rc 和 RefCell 结合使用,实现一个拥有多重所有权的可变数据。

循环引用

使用 Weak 来避免循环引用,并不分享实例的所有权,当 Strong_Reference 数量为0时,值会被清理。相关函数:

  • 调用Rc::downgrade(&rc),返回Weak<T>,并且弱引用计数weak_count加1

  • 调用Weak<T>的 upgrade() 方法,返回Option<Rc<T>>(需要保证指向的值仍然存在)

本文链接: Rust基础(二)

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

发布日期: 2022-05-30

最新构建: 2024-12-26

本文已被阅读 0 次,该数据仅供参考

欢迎任何与文章内容相关并保持尊重的评论😊 !

共 43 篇文章 | Powered by Gridea | RSS
©2020-2024 Nuo. All rights reserved.