Rust 语言学习之旅(7)


Vec 和 String
Vec 是一个智能指针,它只拥有一些字节的内存区域。 Rust 编译器不知道这些字节中存在着什么。 智能指针解释从它管理的内存区域获取数据意味着什么,跟踪这些字节中的数据结构开始和结束的位置,最后将指针解引用到数据结构中, 成为一个漂亮干净的可以阅读的接口供我们使用(例如my_vec[3])。
类似地,String 跟踪字节的内存区域,并以编程方式将写入其中的内容限制为始终有效的 utf-8,并帮助将该内存区域解引用为类型 &str。
这两种数据结构都使用不安全的解引用指针来完成它们的工作。
内存细节:
  • Rust 有一个相当于 C 的 malloc方法, alloc 和 Layout 来获取你自己管理的内存区域。

use std::alloc::{alloc, Layout};
use std::ops::Deref;

struct Pie {
    secret_recipe: usize,
}

impl Pie {
    fn new() -> Self {
        // let's ask for 4 bytes
        let layout = Layout::from_size_align(4, 1).unwrap();

        unsafe {
           
// 分配内存位置并将其保存为数字
            let ptr = alloc(layout) as *mut u8;
           
// 使用指针数学并写一些
           
// u8 值到内存
            ptr.write(86);
            ptr.add(1).write(14);
            ptr.add(2).write(73);
            ptr.add(3).write(64);

            Pie { secret_recipe: ptr as usize }
        }
    }
}
impl Deref for Pie {
    type Target = f32;
    fn deref(&self) -> &f32 {
       
// 将 secret_recipe 指针解释为 f32 原始指针
        let pointer = self.secret_recipe as *const f32;
       
// 将其解引用为返回值 &f32
        unsafe { &*pointer }
    }
}
fn main() {
    let p = Pie::new();
   
// 通过取消引用我们的“make a pie”
   
// Pie 结构智能指针
    println!(
"{:?}", *p);
}

Box 是一个可以让我们将数据从栈移动到堆的智能指针。
解引用可以让我们以人类更容易理解的方式使用堆分配的数据,就好像它是原始类型一样。

struct Pie;

impl Pie {
    fn eat(&self) {
        println!("tastes better on the heap!")
    }
}

fn main() {
    let heap_pie = Box::new(Pie);
    heap_pie.eat();
}

引用计数Rc
Rc 是一个能将数据从栈移动到智能指针。 它允许我们克隆其他Rc智能指针,这些指针都具有不可改变地借用放在堆上的数据的能力。
只有当最后一个智能指针被删除时,堆上的数据才会被释放。

use std::rc::Rc;

struct Pie;

impl Pie {
    fn eat(&self) {
        println!("tastes better on the heap!")
    }
}

fn main() {
    let heap_pie = Rc::new(Pie);
    let heap_pie2 = heap_pie.clone();
    let heap_pie3 = heap_pie2.clone();

    heap_pie3.eat();
    heap_pie2.eat();
    heap_pie.eat();

   
// all reference count smart pointers are dropped now
   
// the heap data Pie finally deallocates
}

共享访问RefCel
RefCell 是一个容器数据结构,通常由智能指针拥有,它接收数据并让我们借用可变或不可变引用来访问内部内容。 当您要求借用数据时,它通过在运行时强制执行 Rust 的内存安全规则来防止借用被滥用
只有一个可变引用或多个不可变引用,但不能同时有!
如果你违反了这些规则,RefCell 将会panic。

use std::cell::RefCell;

struct Pie {
    slices: u8
}

impl Pie {
    fn eat(&mut self) {
        println!("tastes better on the heap!");
        self.slices -= 1;
    }
}

fn main() {
   
// RefCell 在运行时验证内存安全性
     
// 注意:pie_cell 不是 mut!
    let pie_cell = RefCell::new(Pie{slices:8});
    
    {
       
// 但我们可以借用可变引用!
        let mut mut_ref_pie = pie_cell.borrow_mut();
    

线程间共享
Mutex 是一种容器数据结构,通常由智能指针持有,它接收数据并让我们借用对其中数据的可变和不可变引用。 这可以防止借用被滥用,因为操作系统一次只限制一个 CPU 线程访问数据,阻塞其他线程,直到原线程完成其锁定的借用。
多线程超出了 Rust 之旅的范围,但 Mutex 是协调多个 CPU 线程访问相同数据的基本部分。
有一个特殊的智能指针 Arc,它与 Rc 相同,除了使用线程安全的引用计数递增。 它通常用于对同一个 Mutex 进行多次引用。

组合智能指针
智能指针看起来可能会存在一些限制,但是我们可以做一些非常有用的结合。
Rc> - 允许克隆多个可以借用堆上不可变数据结构的相同向量的智能指针。
Rc> - 允许多个智能指针可变/不可变地借用相同的结构Foo
Arc> - 允许多个智能指针以 CPU 线程独占方式锁定临时可变/不可变借用的能力。
内存细节:

  • 您会注意到一个包含许多这些组合的主题。 使用不可变数据类型(可能由多个智能指针拥有)来修改内部数据。 这在 Rust 中被称为“内部可变性”模式。 这种模式让我们可以在运行时以与 Rust 的编译时检查相同的安全级别来改变内存使用规则。

use std::cell::RefCell;
use std::rc::Rc;

struct Pie {
    slices: u8,
}

impl Pie {
    fn eat_slice(&mut self, name: &str) {
        println!("{} took a slice!", name);
        self.slices -= 1;
    }
}

struct SeaCreature {
    name: String,
    pie: Rc<RefCell<Pie>>,
}

impl SeaCreature {
    fn eat(&self) {
       
// 使用指向 pie 的智能指针进行可变借用
        let mut p = self.pie.borrow_mut();
       
// take a bite!
        p.eat_slice(&self.name);
    }
}

fn main() {
    let pie = Rc::new(RefCell::new(Pie { slices: 8 }));
   
// ferris 和 sarah 获得了指向 pie 的智能指针的克隆
    let ferris = SeaCreature {
        name: String::from(
"ferris"),
        pie: pie.clone(),
    };
    let sarah = SeaCreature {
        name: String::from(
"sarah"),
        pie: pie.clone(),
    };
    ferris.eat();
    sarah.eat();

    let p = pie.borrow();
    println!(
"{} slices left", p.slices);
}

智能指针是 Rust编程中经常使用的,它可以让我们不必重新创建非常常见的内存使用范式。 有了它,您可以准备好应对最艰难的挑战了!

Rust语言之GoF设计模式