【Rust】4 一文讲解重点 pattern matching | trait | 生命周期 | 闭包 | 迭代器 | 智能指针 | 并发与并行

news2024/11/26 3:36:12

文章目录

  • 一、pattern matching
  • 二、trait
    • 2.1 常见 trait
      • 2.1.1 Copy 和 Clone
      • 2.1.2 PartialEq 和 Eq
      • 2.1.3 PartialOrd 和 Ord
      • 2.1.4 Hash
      • 2.1.5 From, Into, TryFrom, TryInto
    • 2.2 概念
      • 2.2.1 关联类型
      • 2.2.2 关联常量
      • 2.3.3 泛型关联类型
        • 2.3.3.1 示例: 用泛型关联类型, 创建集合工厂
        • 2.3.3.2 在函数式编程中使用泛型关联类型
  • 三 生命周期
    • 3.1 子类型
    • 3.2 高阶生命周期绑定(HRTB): 泛型的泛型
  • 四 闭包
    • 4.1 move 关键字: 强制捕获上下文的所有权
    • 4.2 闭包的类型推断
    • 4.3 闭包的 trait 的表示和存储
      • 4.2.1 示例: 将闭包作为函数参数或返回值
      • 4.2.2 示例: 将闭包存储到数据结构中
  • 五 迭代器
    • 5.1 示例: 消耗迭代器进行遍历的基本方法
    • 5.2 迭代器的 trait
      • 5.2.1 示例: 使用 trait 方法消耗迭代器
      • 5.2.2 示例: 自定义计数器迭代器
      • 5.3.3 展开 for 循环
    • 5.4 迭代器的变换和消耗
      • 5.4.1 示例: 变换和消耗
      • 5.4.2 迭代器的性能
  • 六 智能指针
    • 6.1 智能指针的特征
    • 6.2 智能指针的引用计数RC
    • 6.3 内部可变性
    • 6.3.1 RefCell
        • 6.3.3.1 示例: 上例使用 RefCell 的效果
        • 6.3.3.2 示例: 使用 Rc + RefCell 实现双向链表
  • 七 并发与并行
    • 7.1 进程和线程
    • 7.2 多线程数据访问: 消息传递
    • 7.3 多线程数据访问: 状态共享
    • 7.4 多线程相关 trait: Send 和 Sync
      • 7.4.1 使用 Arc 智能指针
  • 八 unsafe Rust
    • 8.1 裸指针 *const T 和 *mut T
    • 8.2 ManuallyDrop<T>
    • 8.3 NonNull<T>
    • 8.4 MaybeUninit<T>
    • 8.5 其他函数和方法

本地运行 rustup doc 即可打开 rust 官方文档

一、pattern matching

模式匹配

    let a = 0;
    match a {
        0 => println!("zero"),
        _ => println!("non-zero"),
    }
// let 就是一种 pattern match 的表达式
#[warn(unused_variables)]
fn main() {
    struct A {
        x: i32,
        y: &'static str,
        z: B,
    }
    #[derive(Debug)]
    enum B {
        X(i32),
        Y { a: u8, b: u8 },
        Z,
    }
    let a = A {
        x: 0,
        y: "hello",
        z: B::Y { a: 2, b: 3 },
    };
    let A { x, y, z } = a; // 模式匹配(即对 struct 的解构), xyz 都是新的变量, 将 a 赋值给他们(PS: 因为 struct A 中的属性为 xyz, 所以必须命名为 xyz)
    println!("{}, {}, {:?}", x, y, z);
}

// code result:
0, hello, Y { a: 2, b: 3 }
// if let 的 pattern matching 匹配度更高
if let A {
    z: B::Y { a, b }, .. // ..表示忽略其他字段
} = a
{}

if let (x, y, 3) = (10, 11, 12) {} // 若 3 != 12 则直接返回模式匹配

let a = 12;
let s = match a {
    90..=100 => "A", // ..= 会自动转换为 std::ops::RangeInclusive 结构体
    80..=89 => "B",
    70..=89 => "C",
    _ => "D",
};
// ..end 会自动转换为 core::ops::RangeTo, 即 x < end
// start.. 会自动转换为 core::ops::RangeFrom, 即 x > end

二、trait

2.1 常见 trait

2.1.1 Copy 和 Clone

trait core::clone::Clone 是一个对象, 其赋值对象完整的数据(当然也包括该对象的所有权)

所有不可变引用都实现了 Clone trait(因为不可变引用可以被持有多份, 所以可以被 Clone)

Clone is a supertrait of Copy, so everything which is Copy must also implement Clone. If a type is Copy then its Clone implementation only needs to return *self (see the example above).

We can derive a Copy implementation. Clone is also required, as it’s a supertrait of Copy.

  • 因为实现了 Copy 和 Clone, 所以 let b = a 并未转移所有权, 所以最后一行 a 仍可使用(仍在栈上), 如下图:

  • 而若没有实现 Copy, 则会报错, 因为 let b = a 所有权转移后, 无法再访问 a 了, 如下图:

  • 而若显式调用 .clone() 即可通过编译, 如下图:

  • 如果定义了 A.ptr 指向堆上的内存, 则只能用Clone trait 的 clone()

  • 错误用法如下: Copy trait 会造成 a.ptr 和 b.ptr 都指向同一份堆上的内存, 造成垂悬引用, 虽然 rust 编译器不会报错, 但这是不安全的, 而应当用智能指针来解决. 如下图:

2.1.2 PartialEq 和 Eq

Eq implies PartialEq
区别
PartialEq 描述的是部分相等关系(离散数学的偏序关系), 如 float 中 Nan 和 Nan 是无法比较的, 所以 float 是 PartialEq
Eq 描述的是完全相等关系(离散数学的全序关系)

只要实现了 PartialEq, 就可用 a == b 和 a != b 来表达

PartialEq 是离线数学的偏序关系, 若 a == b, 且 b == c, 无法推导出 a == c
Eq 是离线数学的全序关系, 若 a == b, 且 b == c, 能推导出 a == c

如果想表达部分相等关系, 则只实现 PartialEq 即可
如果想表达完全相等关系, 则实现 PartialEq 后, 再用 impl Eq for Book {} 实现默认的 Eq 方法即可

#[warn(dead_code)]
fn main() {
    enum BookFormat {
        Paperback,
        Hardback,
        Ebook,
    }
    struct Book {
        isbn: i32,
        format: BookFormat,
    }
    impl PartialEq for Book {
        fn eq(&self, other: &Self) -> bool {
            self.isbn == other.isbn
        }
    }
    impl Eq for Book {}
    let a = Book {
        isbn: 1,
        format: BookFormat::Paperback,
    };
    let b = Book {
        isbn: 1,
        format: BookFormat::Hardback,
    };
    assert!(a == b)
}
fn main() {
    let f1 = 3.14;
    let f2 = 3.15;
    if f1 == f2 {
        println!("same");
    }
    if f1 != f2 {
        println!("not same");
    }
    is_eq(f1); // 编译不通过, 因为 float 仅实现了 PartialEq, 而因为 Nan 无法比较故未实现 Eq
    is_partial_eq(f1);
}

fn is_eq<T: Eq>(f: T) {}
fn is_partial_eq<T: PartialEq>(f: T) {}

不同类型也可各自实现 PartialEq, 从而比较 type1 == type2, 但是传递性会有问题, 如下例:

#[derive(PartialEq)]
enum BookFormat {
    Paperback,
    Hardback,
    Ebook,
}

#[derive(PartialEq)]
struct Book {
    isbn: i32,
    format: BookFormat,
}

impl PartialEq<BookFormat> for Book {
    fn eq(&self, other: &BookFormat) -> bool {
        self.format == *other
    }
}

impl PartialEq<Book> for BookFormat {
    fn eq(&self, other: &Book) -> bool {
        *self == other.format
    }
}

fn main() {
    let b1 = Book { isbn: 1, format: BookFormat::Paperback };
    let b2 = Book { isbn: 2, format: BookFormat::Paperback };

    assert!(b1 == BookFormat::Paperback);
    assert!(BookFormat::Paperback == b2);

    // The following should hold by transitivity but doesn't.
    assert!(b1 == b2); // <-- PANICS
}

2.1.3 PartialOrd 和 Ord

PartialOrd 是离线数学的偏序关系, 若 a < b, 且 b < c, 无法推导出 a < c
Ord 是离线数学的全序关系, 若 a < b, 且 b < c, 能推导出 a < c

2.1.4 Hash

#[derive(Hash)]
struct Rustacean {
    name: String,
    country: String,
}

也可以自定义用某些字段做 Hash, 示例如下:

use std::hash::{Hash, Hasher};

struct Person {
    id: u32,
    name: String,
    phone: u64,
}

impl Hash for Person {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.phone.hash(state);
    }
}

2.1.5 From, Into, TryFrom, TryInto

TryFrom 定义了错误处理:

pub trait TryFrom<T>: Sized {
    type Error;

    // Required method
    fn try_from(value: T) -> Result<Self, Self::Error>;
}
struct GreaterThanZero(i32);

impl TryFrom<i32> for GreaterThanZero {
    type Error = &'static str;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value <= 0 {
            Err("GreaterThanZero only accepts values greater than zero!")
        } else {
            Ok(GreaterThanZero(value))
        }
    }
}
let big_number = 1_000_000_000_000i64;
// Silently truncates `big_number`, requires detecting
// and handling the truncation after the fact.
let smaller_number = big_number as i32;
assert_eq!(smaller_number, -727379968);

// Returns an error because `big_number` is too big to
// fit in an `i32`.
let try_smaller_number = i32::try_from(big_number);
assert!(try_smaller_number.is_err());

// Returns `Ok(3)`.
let try_successful_smaller_number = i32::try_from(3);
assert!(try_successful_smaller_number.is_ok());

2.2 概念

本质: 表示一组特定的功能
包含: 方法, 关联类型, 常量

2.2.1 关联类型

例如如下自行实现的一个 函数 trait:

fn main() {
    trait Function<Args> {
        // Args: 输入参数
        type Output; // Output: 关联类型
        fn call(args: Args) -> Self::Output;
    }

    struct AddFunction; // 单元结构体, 其内存布局相当于()

    // 两个数的加法, 入参为(T, T), 关联类型为 Output = T
    impl<T: std::ops::Add<Output = T>> Function<(T, T)> for AddFunction {
        type Output = T;
        fn call(args: (T, T)) -> Self::Output {
            args.0 + args.1
        }
    }

    // 三个数的加法, 入参为(T, T), 关联类型为 Output = T
    impl<T: std::ops::Add<Output = T>> Function<(T, T, T)> for AddFunction {
        type Output = T;
        fn call(args: (T, T, T)) -> Self::Output {
            args.0 + args.1 + args.2
        }
    }

    // 虽两种 AddFunction 的泛型实现, 但关联类型 Output 只有一种
    assert_eq!(AddFunction::call((1, 2)), 3);
    assert_eq!(AddFunction::call((1, 2, 3)), 6);
}

2.2.2 关联常量

// 下文抽象了寄存器访问内存地址
use std::marker::PhantomData;

fn main() {
    //------如下为定义------
    enum Access {
        Read,
        Write,
        ReadWrite,
    }
    trait MmioReg {
        const OFFSET: usize; // 关联常量
        const ACCESS: Access; // 关联常量
        type Repr: Sized + Copy; // 关联类型 Repr 意为寄存器

        fn at(base: std::ptr::NonNull<()>) -> Volatile<Self::Repr> {
            let _address = base.as_ptr() as usize + Self::OFFSET;
            Volatile(Self::ACCESS, PhantomData)
        }
    }
    struct Volatile<T>(Access, std::marker::PhantomData<T>); // struct, 标识用什么方式(Access), 读写一个内存指针(PhantomData)

    //------如下为使用------
    struct SampleReg;
    impl MmioReg for SampleReg {
        const OFFSET: usize = 0x100;
        const ACCESS: Access = Access::ReadWrite;
        type Repr = u32;
    }
    let reg = SampleReg::at(std::ptr::NonNull::dangling());
}

2.3.3 泛型关联类型

trait 内的关联类型, 本身也可以是泛型的

2.3.3.1 示例: 用泛型关联类型, 创建集合工厂
fn main() {
    // -------定义trait如下
    trait CollectionFactory {
        type Collection<T>;
        type Iter<'a, T>
        where
            T: 'a;

        fn empty<T>() -> Self::Collection<T>;

        fn iter<'a, T>(collection: &'a Self::Collection<T>) -> Self::Iter<'a, T>
        where
            T: 'a;
    }

    // -------实现trait如下
    struct VecFactory;
    impl CollectionFactory for VecFactory {
        type Collection<T> = Vec<T>;
        type Iter<'a, T> = std::slice::Iter<'a, T> where T: 'a;

        fn empty<T>() -> Self::Collection<T> {
            Vec::new()
        }

        fn iter<'a, T>(collection: &'a Self::Collection<T>) -> Self::Iter<'a, T>
        where
            T: 'a,
        {
            collection.iter()
        }
    }

    // ------使用如下, 传入 T 和 CollectionFactory
    struct Queue<T, F: CollectionFactory> {
        inner: F::Collection<T>,
    }
    let vec_based_queue = Queue::<_, VecFactory> {
        inner: vec![1, 2, 3],
    };
}
2.3.3.2 在函数式编程中使用泛型关联类型
fn main() {
    trait Functor<T> {
        type Output<U>: Functor<U>;
        fn fmap<U>(self, f: impl FnOnce(T) -> U) -> Self::Output<U>;
    }
    impl<T> Functor<T> for Option<T> {
        type Output<U> = Option<U>;
        fn fmap<U>(self, f: impl FnOnce(T) -> U) -> Self::Output<U> {
            self.map(f)
        }
    }
    let s = Some(1);
    let s = s.fmap(|x| x + 1);
    assert_eq!(s, Some(2));
}

三 生命周期

本质: 一个结构单元, 其包括了一个变量/类型的存活范围

fn main() {
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
    let l = longest("a", "abc");
    assert_eq!(l, "abc");
}
fn main() {
    struct Ref<'a, T>(&'a T);
    let x = 42;
    let y = Ref(&x);
    assert_eq!(y.0, &42);
}

3.1 子类型

  • 生命周期的包含关系: 比 … 活得久(outlive)
    • 结论: 'a: 'b 等价于 'a 比 'b 活得久, 即 a outlives b
    • 定理: 对于任意生命周期 'a, 有 'static: 'a (即 staitc 生命周期 比 a 生命周期 活的更久)
  • 子类型: 可替换性
    • 定义: 'x 是 'y 的子类型
      • 大前提: 对于某处 “不可变引用”, 有对于单个生命周期的要求
      • 小前提: 若 'y 满足该要求
      • 结论: 'x 也满足该要求
    • 暴论: 类型越"子", 可以满足的要求越多
  • 结合以上两组结论, 我们有 'a: 'b 等价于 'a 是 'b 的子类型

'a 是 'b 的子类型 表示为 偏序关系 <, 'a 与 'b 无关 表示为 'a <> 'b, 则对于任意两个生命周期 'a 和 'b, 仅存在 3 种不等关系, 将这3种关系统称为 R('a, 'b):

  • 'a < 'b, 即 a 是 b 的子类型
  • 'a <> 'b, 即 a 与 b 无关
  • 'a > 'b, 即 b 是 a 的子类型

则 R 对于泛型的映射

  • 对于单生命周期的泛型 T, 这个映射即定义为 T: R('a, 'b) --> R<'a>, T<'b>), 这个关系有三种:
    • 相同: 即若 'a 是 'b 的子类型, 则 T<'a> 是 T<'b> 的子类型
    • 相反: 即若 'a 是 'b 的子类型, 则 T<'b> 是 T<'a> 的子类型
    • 无关: 即若 'a 是 'b 的子类型, 则 T<'a> 和 T<'b> 无关

示例如下:

fn main() {
    struct A<'a>(&'a i32); // A即为一个单生命周期的类型构造器

    let x = 1;
    // 外括号的生命周期为'a
    {
        let y: &i32 = &x;
        // 内括号的生命周期为'b
        {
            let z: &i32 = &x;
        }
    }
    // 因为外括号的存活域比内括号的存活域大, 设外括号的生命周期为'a, 设内括号的生命周期为'b
    // 则 'a 是 'b 的子类型
    // 则对于实际构造出的两个引用(y 和 z), 即有 y 是生命周期为'a 的 &i32, z 是生命周期为'b 的 &i32
}

更详细的解释是:

fn main() {
    struct A<'a>(&'a i32); // A即为一个单生命周期的类型构造器

    let x = 1;
    // 外括号的生命周期为'a
    {
        let mut y: &i32 = &x;
        // 内括号的生命周期为'b
        {
            let mut z: &i32 = &x;
            y = z; // 可编译通过
            z = y; // 可编译通过
        }
    }
    // 因为外括号的存活域比内括号的存活域大, 设外括号的生命周期为'a, 设内括号的生命周期为'b
    // 则 'a 是 'b 的子类型
    // 则对于实际构造出的两个引用(y 和 z), 即有 y 是生命周期为'a 的 &i32, z 是生命周期为'b 的 &i32

    // 则 y 的生命周期为 'a, z 的生命周期为 'b
    // 则生命周期为 'b 的 &i32, 可以被 生命周期为 'a 的 &i32 替换
    // 生命周期为 'a 的 &i32, 就是生命周期为 'b 的 &i32 (子类型 a 就是父类型 b)

    // 构成了一种映射: 若 'a 是 'b 的子类型, 则 &'a &i32 是 &'b &i32 的子类型
}

总结三种映射关系如下:

  • 不可变引用, 是协变关系 (示例如上)
    • 如 &'static str 是 &str 的子类型, 即 &'static str 可替换 &str
  • 函数, 是逆变关系
    • 如 fn(&'static str) 并不是 fn(&'x str) 的子类型, 即不能用 fn(&'static str) 替换 fn(&'x str).
    • 但反而 fn(&'x str) 是 fn(&'static str) 的子类型, 即能用 fn(&'x str) 替换 fn(&'static str)
  • 可变引用, 是不变关系

示例: 如下图即为悬垂引用的示例

  • 用传统 rust 话术是这样解释的: 因为 hello 是 static 生命周期, 而 world 只有 'x 的生命周期, 所以 当将 hello 指向 world 的引用时, rust 编译器会报错: world does not live long enough, 即当 world 走出其作用域而失效时, 会造成 hello 指向悬垂引用:
  • 另一种解释话术如下:

详见 file:///Users/y/.rustup/toolchains/stable-aarch64-apple-darwin/share/doc/rust/html/nomicon/subtyping.html?highlight=subtyping#subtyping

3.2 高阶生命周期绑定(HRTB): 泛型的泛型

四 闭包

本质: 拥有可能的关联上下文的匿名函数结构体

fn main() {
    let x = 5;
    let plus_x = |a| a + x;
    assert_eq!(plus_x(5), 10);
}

闭包实际展开如下:

#[test]
fn expansion() {
    struct Anonymous<'a>(&'a i32);
    impl<'a> Anonymous<'a> {
        // 捕获
        fn new(x: &'a i32) -> Self {
            Anonymous(x)
        }
        // 使用
        fn call(&self, a: i32) -> i32 {
            a + self.0
        }
    }
    let x = 5;
    let plus_x = Anonymous::new(&x);
    assert_eq!(plus_x.call(5), 10);
}

4.1 move 关键字: 强制捕获上下文的所有权

  • 不使用 move 时如下:
#[test]
fn use_keyword_move() {
    let vec = vec![1, 2, 3];
    {
        let print_borrowed_vec = || println!("{vec:?}"); // 不使用 move 关键字时, 闭包是以引用方式捕获的
        println!("{vec:?}");
        print_borrowed_vec()
    }
}
  • 使用 move 时如下:
#[test]
fn use_keyword_move() {
    let vec = vec![1, 2, 3];
    {
        let print_owned_vec = move || println!("{vec:?}");
        println!("{vec:?}"); // 编译报错, 因vec 的所有权已在上一行被转移了
        print_owned_vec();
    }
}

move 其实是一个语法糖, 我们也可自己捕获, 如下例是等价的两个闭包:

fn use_keyword_move() {
    let vec = vec![1, 2, 3];
    // {
    //     let print_owned_vec = move || println!("{vec:?}");
    //     print_owned_vec();
    // }
    {
        let print_owned_vec = || {
            let vec = vec; // vec 捕获了所有权
            println!("{vec:?}");
        };
        print_owned_vec();
    }
}

4.2 闭包的类型推断

4.3 闭包的 trait 的表示和存储

// FnOnce 示例如下:
let print_owned_vec = move || println!("{vec:?}"); // 是FnOnce, 即调用一次就会消耗其所有权

// FnMut 示例如下:
    struct Anonymous<'a>(&'a mut i32); // 是 &mut, 所以每调用一次都会使其上下文发生改变, 则它只实现了 FnMut, 而没有实现 Fn
    impl<'a> Anonymous<'a> {
        // 捕获
        fn new(x: &'a i32) -> Self {
            Anonymous(x)
        }
        // 使用
        fn call(&self, a: i32) -> i32 {
            a + self.0
        }
    }

// Fn 示例如下:
let plus_x = |a| a + x; // 是Fn, 因为获取的都是不可变引用, 所以其可被无损的调用任意次

他们之间是包含关系:

  • 只要实现了 Fn, 则一定也实现了 FnMut 和 FnOnce
  • 只要实现了 FnMut, 则一定也实现了 FnOnce

4.2.1 示例: 将闭包作为函数参数或返回值

#[test]
fn pass_as_parameters_and_return_values() {
    fn plus(x: i32) -> impl Fn(i32) -> i32 {
        move |a| a + x
    }
    fn map<T, U>(value: T, f: impl FnOnce(T) -> U) -> U {
        f(value)
    }
    fn assert_fn_once<F: FnOnce(i32) -> i32>(_: F) {}
    fn assert_fn_mut<F: FnMut(i32) -> i32>(_: F) {}

    let x = 5;
    let plus_x = plus(x); // 因为 plus_x 实现了 Fn, 又因为若实现了 Fn 则一定也实现了 FnMut 和 FnOnce
    assert_fn_once(&plus_x); // 所以 plus_x 实现了 FnOnce, 所以此断言通过
    assert_fn_mut(&plus_x);
    assert_eq!(map(2, plus_x), 7);
}

4.2.2 示例: 将闭包存储到数据结构中


#[test]
fn storage() {
    // -----定义:
    struct ServiceBuilder<Opener> {
        params: Option<String>,
        opener: Opener,
    }
    impl<Opener> ServiceBuilder<Opener> {
        fn new(opener: Opener) -> Self {
            Self {
                params: None,
                opener,
            }
        }
        fn with_params(mut self, params: String) -> Self {
            self.params = Some(params);
            self
        }
    }
    impl<Service, Opener: FnOnce(String) -> Service> ServiceBuilder<Opener> {
        fn open(self) -> Service {
            let params = self.params.expect("Please input some params");
            (self.opener)(params)
        }
    }

    // -----使用:
    let opener = |params| {
        println!("Opening service with params: {params}");
        "mocked service"
    };
    let init_service = ServiceBuilder::new(opener).with_params("Some params".to_string());
    let service = init_service.open();
    println!("Service is {service:?}");
}

// output result as belows:
Opening service with params: Some params
Service is "mocked service"
test storage ... ok

五 迭代器

本质: 对序列的流失处理–变换或消耗

5.1 示例: 消耗迭代器进行遍历的基本方法

#[test]
fn basic_usage() {
    let array = [1, 2, 3, 4, 5];
    let iter = array.iter();
    for item in iter {
        println!("{item}");
    }
}

5.2 迭代器的 trait

5.2.1 示例: 使用 trait 方法消耗迭代器

#[test]
fn iterator_traits() {
    let array = [1, 2, 3, 4, 5];
    let mut iter = array.iter();
    assert_eq!(iter.next(), Some(&1));
    assert_eq!(iter.next(), Some(&2));
    assert_eq!(iter.next(), Some(&3));
    assert_eq!(iter.next(), Some(&4));
    assert_eq!(iter.next(), Some(&5));
    assert_eq!(iter.next(), None);
    assert_eq!(iter.next(), None);
}

5.2.2 示例: 自定义计数器迭代器

#[test]
fn custom_iterator() {
    struct Counter {
        start: i32,
        count: i32,
    }
    impl Iterator for Counter {
        type Item = i32;
        fn next(&mut self) -> Option<Self::Item> {
            if self.count > 0 {
                let ret = self.start;
                self.start += 1;
                self.count -= 1;
                Some(ret)
            } else {
                None
            }
        }
    }
    let mut counter = Counter { start: 3, count: 5 };
    for item in counter {
        println!("{item}",);
    }
}

// code result:
3
4
5
6
7

5.3.3 展开 for 循环


#[test]
fn expand_for_loop() {
    let array = [1, 2, 3, 4, 5];
    for item in array.iter() {
        println!("{item}");
    }

	// 可展开为 while let
    {
        let mut iter = array.iter();
        while let Some(item) = iter.next() {
            println!("{item}");
        }
    }

	// 也可展开为 loop match
    let mut iter = array.iter();
    loop {
        match iter.next() {
            Some(item) => println!("{item}"),
            None => break,
        }
    }
}

5.4 迭代器的变换和消耗

迭代器是 rust 函数式编程经常使用的场合

5.4.1 示例: 变换和消耗

fn main() {
    struct Person {
        name: String,
        age: i32,
    }
    let people = [
        Person {
            name: "Alice".to_string(),
            age: 21,
        },
        Person {
            name: "Bob".to_string(),
            age: 25,
        },
        Person {
            name: "Charlie".to_string(),
            age: 18,
        },
        Person {
            name: "Dave".to_string(),
            age: 30,
        },
        Person {
            name: "Eve".to_string(),
            age: 28,
        },
        Person {
            name: "Frank".to_string(),
            age: 32,
        },
    ];
    let student_ids = [1001, 1002, 1003, 1004, 1005, 1006];

    let all_names = people.iter().map(|p| p.name.clone()).collect::<Vec<_>>();
    println!("{:?}", all_names);

    // let first_3_student_ids_whose_age_is_over_21
    let mut first_3_student_ids_whose_age_is_over_21 = Vec::new();
    for index in 0..6 {
        let person = &people[index];
        let id = student_ids[index];
        if person.age > 21 {
            first_3_student_ids_whose_age_is_over_21.push(id);
            if first_3_student_ids_whose_age_is_over_21.len() == 3 {
                break;
            }
        }
    }
    assert_eq!(first_3_student_ids_whose_age_is_over_21, [1002, 1004, 1005]);
    let first_3_student_ids_whose_age_is_over_21 = people
        .iter()
        .zip(student_ids.iter())
        //.filter(|(p, _)| p.age > 21)
        //.map(|(_, id)| *id).
        .filter_map(|(p, &id)| (p.age > 21).then_some(id))
        .take(3)
        .collect::<Vec<_>>();
    assert_eq!(first_3_student_ids_whose_age_is_over_21, [1002, 1004, 1005]);
}

5.4.2 迭代器的性能

迭代器性能很好, 程序员不需要为了性能而使用 for, 使用 迭代器即可兼顾性能和可读性

六 智能指针

fn main() {}
#[test]
fn basic_usage() {
    let x = 5;
    let boxed_x = Box::new(x); // Box将无法在编译期确定大小的东西, 变为可在编译器确定大小
    let y = *boxed_x; // 解引用
    let z = *boxed_x;
    assert_eq!(y, z);
}
  • Box
use std::collections::HashMap;
fn main() {}
#[test]
fn basic_usage() {
    let x = 5;
    let boxed_x = Box::new(x);
    let y = *boxed_x;
    let z = *boxed_x;
    assert_eq!(y, z);

    let array = [1, 2, 3, 4, 5];
    let mut iter = Box::new(array.into_iter()) as Box<dyn Iterator<Item = i32>>;
    let hash_map = [(1, ()), (2, ())].into_iter().collect::<HashMap<_, _>>();
    iter = Box::new(hash_map.into_keys());
}
  • vec
fn main() {}
#[test]
fn basic_usage() {
    let x = 5;
    let boxed_x = Box::new(x);
    let y = *boxed_x;
    let z = *boxed_x;
    assert_eq!(y, z);

    let array = [1, 2, 3, 4, 5];
    let slice = &array[1..3];
    let vec = slice.to_vec();
    let vec_slice = &vec[..];
    assert_eq!(vec_slice, slice);
}
  • String
fn main() {}
#[test]
fn basic_usage() {
    let x = 5;
    let boxed_x = Box::new(x);
    let y = *boxed_x;
    let z = *boxed_x;
    assert_eq!(y, z);

    let s = "Hello world";
    let s_slice = &s[1..3];
    let string = s.to_string();
    let string_slice = &string[1..3];
    assert_eq!(string_slice, s_slice);
}

6.1 智能指针的特征

源码中 String 的 Deref 实现如下:

  • 自己实现的 智能指针 Smart 如下, 主要是实现了 Deref trait:
use std::ops::Deref;

fn main() {}

#[test]
fn custom_smart_pointers() {
    struct Smart<T>(T);
    impl<T> Smart<T> {
        fn new(value: T) -> Self {
            Self(value)
        }
    }
    impl<T> Deref for Smart<T> {
        type Target = T;
        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }
    let smart = Smart::new(5);
    assert_eq!(*smart, 5)
}
  • 自己实现的 智能指针 Smart 如下, 除了实现 Deref trait, 还实现了 DerefMut trait:
use std::ops::{Deref, DerefMut};

fn main() {}

#[test]
fn custom_smart_pointers() {
    struct Smart<T>(T);
    impl<T> Smart<T> {
        fn new(value: T) -> Self {
            Self(value)
        }
    }
    impl<T> Deref for Smart<T> {
        type Target = T;
        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }
    impl<T> DerefMut for Smart<T> {
        fn deref_mut(&mut self) -> &mut Self::Target {
            &mut self.0
        }
    }
    let mut smart = Smart::new(5);
    assert_eq!(*smart, 5);
    *smart = 10;
    assert_eq!(*smart, 10);
}

6.2 智能指针的引用计数RC

use std::rc::Rc;

fn main() {
    let c = Node::new(1, None);
    let b = Node::new(2, Some(Rc::clone(&c)));
    let a = Node::new(3, Some(Rc::clone(&b)));
    let x = Node::new(4, Some(Rc::clone(&a)));
    let y = Node::new(5, Some(Rc::clone(&a)));
    // 因为 x 和 y 都共享 a, 所以有两条链表, 分别是 xabc 和 yabc
    let vec_x = x.to_vec();
    assert_eq!(vec_x, vec![4, 3, 2, 1]);
    let vec_y = y.to_vec_iterated();
    assert_eq!(vec_y, vec![5, 3, 2, 1]);
}

struct Node<T> {
    data: T,
    next: Option<Rc<Node<T>>>,
}

impl<T> Node<T> {
    fn new(data: T, next: Option<Rc<Node<T>>>) -> Rc<Self> {
        Rc::new(Node { data, next })
    }
    fn to_vec(self: &Rc<Self>) -> Vec<T>
    where
        T: Clone,
    {
        let mut vec = Vec::new();
        let mut node = Some(Rc::clone(self));
        while let Some(n) = node {
            vec.push(n.data.clone());
            node = n.next.as_ref().map(Rc::clone);
        }
        vec
    }
    // -----使用迭代器如下:
    fn to_vec_iterated(self: &Rc<Self>) -> Vec<T>
    where
        T: Clone,
    {
        let mut node = Some(Rc::clone(self));
        let iter = std::iter::from_fn(move || match node.take() {
            Some(n) => {
                node = n.next.as_ref().map(Rc::clone);
                Some(n.data.clone())
            }
            None => None,
        });
        iter.collect()
    }
}

6.3 内部可变性

fn main() {}

#[test]
fn interior_mutability() {
    #![allow(unused)]
    trait MessageSender {
        fn send(&self, message: String); // &self 是不可变引用
    }
    struct Validator<M: MessageSender>(M);

    impl<M: MessageSender> Validator<M> {
        fn validate(&self, value: &i32) {
            if !(-10..10).contains(value) {
                self.0
                    .send(format!("Value {} is not in range [-10, 10]", value));
            }
        }
    }

    struct MockMessageSender {
        messages: Vec<String>,
    }

    impl MessageSender for MockMessageSender {
        fn send(&self, message: String) {
            self.messages.push(message); // 因为需要调用 push(), 所以需要 self 内部的 messages 是可变的, 而 &self 本身是不可变引用, 所以需要内部可变性
        }
    }
}

6.3.1 RefCell

本质是先在编译期通过, 但运行时如果发生错误仍会直接 panic(), 所以非常坏的一种实践

6.3.3.1 示例: 上例使用 RefCell 的效果

6.3.3.2 示例: 使用 Rc + RefCell 实现双向链表

七 并发与并行

7.1 进程和线程

  • 示例: 使用 std::thread 创建线程, 使用 JoinHandle() 等待线程完成
use std::thread;

fn main() {}

#[test]
fn use_multithread() {
    let thread = thread::spawn(|| {
        for i in 0..10 {
            println!("thread: {}", i);
        }
    });
    for i in 0..10 {
        println!("main: {}", i);
    }
    thread.join().unwrap();
}

// code result:
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
thread: 0
thread: 1
thread: 2
thread: 3
thread: 4
thread: 5
thread: 6
thread: 7
thread: 8
  • 示例: 使用 move 在多线程间用闭包捕获所有权
use std::thread;

fn main() {}

#[test]
fn move_between_threads() {
    let vec = vec![1, 2, 3, 4, 5];
    let thread = thread::spawn(move || {
        let mut vec = vec;
        vec.push(6);
        println!("{vec:?}");
    });
    // println!("{vec:?}"); // 此处无法再使用 vec, 因为其所有权已被 move 到线程里了
    thread.join().unwrap();
}

// code result:
[1, 2, 3, 4, 5, 6]

7.2 多线程数据访问: 消息传递

uuse std::{sync::mpsc, thread};

fn main() {}

#[test]
fn mpsc_channels() {
    let (tx, rx) = mpsc::channel();
    for t in 0..10 {
        let tx = tx.clone();
        thread::spawn(move || {
            for i in 0..3 {
                let num = 100 * t + i;
                tx.send(num).unwrap();
            }
        });
    }
    drop(tx); // drop main()函数中的 tx
              // 当所有发送端都发送完毕后, 则 mpsc 的所有 rx 都会自动 drop, 则rx.recv() 会返回 Err
    while let Ok(num) = rx.recv() {
        println!("{}", num);
    }
}

7.3 多线程数据访问: 状态共享

use std::{
    sync::{mpsc, Mutex},
    thread,
    time::Duration,
};

fn main() {}

#[test]
fn mutexes() {
    static DATA: Mutex<i32> = Mutex::new(0);
    thread::spawn(|| {
        for i in 0..10 {
            *DATA.lock().unwrap() += i;
            thread::sleep(Duration::from_secs(1));
        }
    });
    while *DATA.lock().unwrap() < 45 {
        println!("Waiting");
        thread::sleep(Duration::from_secs(1));
    }
}

// code result: 打印 10 次 Waiting 后值大于 45, 故退出 main() 函数
running 1 test
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
test mutexes ... ok

7.4 多线程相关 trait: Send 和 Sync

7.4.1 使用 Arc 智能指针

Arc 即 Atomic 的 Rc, 可在线程间使用

use std::{
    sync::{mpsc, Mutex, Arc},
    thread,
    time::Duration,
};

fn main() {}

#[test]
fn arc_usage() {
    let data = Arc::new(Mutex::new(0));
    let d2 = data.clone();

    thread::spawn(move || {
        for i in 0..10 {
            *d2.lock().unwrap() += i;
            thread::sleep(Duration::from_secs(1));
        }
    });
    while *data.lock().unwrap() < 45 {
        println!("Waiting");
        thread::sleep(Duration::from_secs(1));
    }
}

Arc 源码如下:

八 unsafe Rust

unsafe 块一定要写注释, 示例如下:

8.1 裸指针 *const T 和 *mut T

8.2 ManuallyDrop

8.3 NonNull

8.4 MaybeUninit

8.5 其他函数和方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1122803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浙大计算机研究生复试上机考试-2005年畅通工程(考察并查集)

文章目录 并查集知识畅通工程实现代码样例运行结果 并查集知识 畅通工程 题目原地址 某省调查城镇交通状况&#xff0c;得到现有城镇道路统计表&#xff0c;表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通&#xff08;但不…

远程监控高并发高吞吐java进程

文章目录 背景工具jconsole和jvisualvm 压测实战以太坊Java程序监控1.使用jconsole监控2.使用jvisualvm监控 问题分析堆内存使用异常通过调整内存策略来应对&#xff1a; 交易虚增问题 背景 作为使用java技术栈的金融类公司&#xff0c;确保Java程序在生产环境中的稳定性和性能…

【电子通识】USB TYPE-A 2.0/3.0连接器接口

基础知识 USB TYPE-A连接器又可称为USB-A&#xff0c;现在不少PC、PC周边、手机充电器等等都依然采用了这种扁平的矩形接口&#xff0c;是目前普及度最高的USB接口了。 USB-A亦有分为插头与插座。常见的USB-A数据线的A端就是插头&#xff0c;而充电器上的则是插座。插头和插座…

buuctf[极客大挑战 2019]Havefun 1

网页环境title标题每一帧都不要放过&#xff0c;或许那个不起眼的地方就存在重要信息到这并未发现什么重要信息&#xff0c;F12看看在源代码底部发现PHP代码&#xff1a; <!-- $cat$_GET[cat]; echo $cat; if($catdog){ echo Syc{cat_cat_cat_cat}; } --> PHP代码…

自然语言处理---Transformer机制详解之Multi head Attention详解

1 采用Multi-head Attention的原因 原始论文中提到进行Multi-head Attention的原因是将模型分为多个头, 可以形成多个子空间, 让模型去关注不同方面的信息, 最后再将各个方面的信息综合起来得到更好的效果.多个头进行attention计算最后再综合起来, 类似于CNN中采用多个卷积核的…

高效使用python之xlwt库编辑写入excel表内容

头条号&#xff1a;科雷软件测试 学习目录 了解下电脑中的excel表格文件格式 安装xlwt库 xlwt库写入表格内容 1 导入xlwt库 2 用一个图展示下xlwt常用的函数 3 往表格写入一些内容并保存 4 设置样式 1 先初始化XFStyle 2 设置字体font 3 设置边框 4 设置对齐方式 …

word/ppt/excel出现错误代码:0x426-0x0

一、问题描述 突然打开Microsoft的软件时出现了这种错误 二、解决办法 按住winr&#xff0c;win就是键盘上四个方块组成的键。 然后输入“control” 找到程序和功能&#xff0c;然后点击 找到Microsoft Office 家庭和学生版2021-zh-cn 注&#xff1a;你的版本版本可能不是这个…

Java IO输入输出流 第15章

Java I/O输入/输出流 第15章 1.输入/输出流 Java I/O&#xff08;输入/输出&#xff09;流是用于在Java程序中处理输入和输出数据的机制。这是与文件、网络连接、键盘、屏幕等各种数据源和数据目标进行交互的重要方式。Java的I/O库提供了一组类来处理各种I/O操作&#xff0c;…

【计算机网络笔记】TCP/IP参考模型基本概念,包括五层参考模型

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

概率论_概率公式中的分号(;)、逗号(,)、竖线(|)

1. 概率公式中的分号(;)、逗号(,)、竖线(|) ; 分号代表前后是两类东西&#xff0c;以概率P(x;θ)为例&#xff0c;分号前面是x样本&#xff0c;分号后边是模型参数。 , 逗号代表两者地位平等&#xff0c;代表与的关系 | 竖线代表 if&#xff0c;以条件概率P(A|B)为例&#xff0…

【数据结构】String类对象的创建与字符串常量池的“神秘交易”

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力…

【码制】原码反码补码移码浮点数

从C语言占位符到码值 学C语言的时候一定会用到printf("%d",a); 有的课程称%d为“占位符”&#xff0c;非常形象&#xff1a;%d替a占位&#xff0c;输出的时候a的值会替换%d的内容。 但也有课程称之为“转换规范”&#xff0c;官方称之为“format specifiers”格式说…

Git最佳实践:git常用命令和原理

Git 是一个开源的分布式版本控制系统。 Git 工作区、暂存区和版本库 工作区&#xff1a;就是你在电脑里能看到的目录。暂存区&#xff1a;英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件&#xff08;.git/index&#xff09;中&#xff0c;所以我们把暂存区有时…

7.20 SpringBoot项目实战【图书详情-学生端】:图书信息 + 评论列表 + 是否收藏

文章目录 前言一、接口规划二、编写服务层三、编写数据访问层四、编写控制器五、PostMan测试1. getBook 根据id获取图书2. getBookCommentList 根据id获取图书详情 - 评论列表3. getFavoriteId 获取学生收藏了某图书的收藏id 最后 前言 学生的【借阅申请】审核通过以后&#x…

nodejs+vue市民健身中心网上平台-计算机毕业设计

市民健身中心网上平台分为用户界面和管理员界面&#xff0c;用户界面功能模块图如图1所示&#xff0c;管理员界面功能模块图如图2所示。 目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs…

springboot+avue医院绩效考核系统源码

医院绩效考核系统是一种以人力资源管理为基础&#xff0c;选用适合医院组织机构属性的绩效理论和方法&#xff0c;基于医院战略目标&#xff0c;构建全方位的绩效考评体系&#xff0c;在科学、合理的绩效管理体系基础上&#xff0c;采用科学管理的方法&#xff0c;如平衡计分卡…

C++前缀和算法:生成数组原理、源码及测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划&#xff0c;日后完成。 题目 给定三个整数 n、m 和 k 。考虑使用下图描述的算法找出正整数数组中最大的元素。 请你构建一个具有以下属性的数组 arr &#…

Amazon CodeWhisperer让力扣不再用力code!

书接上文《爱编程 why not AI编程》&#xff0c;通过前文的对于Amazon CodeWhisperer的介绍、入门指南、相关课程以及【云上探索实验室】活动&#xff0c;各位读者们应该对于Amazon CodeWhisperer有了一些了解&#xff0c;那么作者今天将该通过本篇文章来介绍用Amazon CodeWhis…

OpenCV官方教程中文版 —— Canny 边缘检测

OpenCV官方教程中文版 —— Canny 边缘检测 前言一、原理1.噪声去除2.计算图像梯度3.非极大值抑制4.滞后阈值 二、OpenCV 中的 Canny 边界检测练习 前言 OpenCV 中的 Canny 边缘检测 • 了解 Canny 边缘检测的概念 • 学习函数 cv2.Canny() 一、原理 Canny 边缘检测是一种…

FreeRTOS深入教程(任务的引入及栈的作用)

文章目录 前言一、任务的引入二、深入理解C语言函数的调用1.ARM架构2.基础汇编指令3.函数运行流程分析 三.保存现场的几种情况1.函数调用2.中断处理3.任务切换 总结 前言 本篇文章开始带大家深入学习FreeRTOS&#xff0c;带大家学习什么是任务&#xff0c;并且深入学习栈的作用…