如何玩转Rust语言-Meetup笔记

本文探讨了Rust的设计哲学,如内存安全、线程安全和零成本抽象。详细介绍了通过所有权和借用解决内存安全问题,包括只读借用、可变借用及其规则。此外,还讨论了Rust的并发特性,如无畏并发,以及在处理双向链表等复杂数据结构时可能遇到的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

设计哲学

1 现有 c c++ 程序中内存安全问题分类

2 Rust 解决内存安全的解决方法:所有权 + 借用

所有权

借用

只读借用(&)

可变借用(&mut)

可变性

生命周期

拒绝悬垂指针

拒绝迭代器失效

借用 + 生命周期 = 痛苦之源

并发安全问题

泛型与Trait

Trait的作用

数据竞争

Rust中多线程

无畏并发

双向链表困境


设计哲学

安全:内存安全与线程安全

       内存安全:垃圾回收,自动引用计数

        线程安全: 单线程, CSP模型,Actor模型

零成本抽象:不会为没使用的功能付出代价

实用性:能够用在各种场合; 类型推断,范型支持,模式匹配 ... other goodies

 

1 现有 c c++ 程序中内存安全问题分类

1、Dangling Pointer

2、Use After Free

3、Double Free

typedef struct Dummy {int a; int b; } Dummy;

void foo(void) {
    Dummy *ptr = (Dummy *) malloc(sizeof (struct Dummy));
    Dummy *alias = ptr;
    free(ptr);
    int a = alias.a;  // when free ptr, alias is become a dangling pointer; and alias is used after free!!
    free(alias); // Double free
}

2 Rust 解决内存安全的解决方法:所有权 + 借用

所有权

1、堆内存中的数据视为资源

2、堆上的资源只有一个所有者

3、当资源被其他代码借用时,资源所有者不可以释放资源或修改资源

4、编译时检查,无运行时开销

fn main () {
    let x = String::from("hello");  //字符串 hello 的所有者为 x
    let y = x;                               //字符串 hello 的所有者变成为 y, x  失去 所有权
   // error[E0382]: use of moved value: 'x'
   drop(x);
}

借用:rust其它代码,可以从资源的所有者处借用到资源,只有使用权,没有所有权

copy语义:

fn main() {
    let x = 0u8;
    let y = x;  //是不会编译报错的,因为立即数,整数具有copy语言 let y = x 时复制了另一份数据给y
    println!("{}", y);
}

借用

    借用本质上是一个指针

    借用分为可变借用,只读(共享)借用,可变借用可以用来修改指向对象的数据

    资源分为可变资源,不可变资源,不充许对不可变资源进行可变借用

    允许同时存在多个只读供借用指向同一对象

    可变借用:拥有被借用对象的独占权,可变借用存在的情况下,不允许其它只读、可变借用存在

    借用的生命周期小于被借用资源的生命周期

struct Dummy {a: i32, b: i32}

fn foo() {
    let mut res = Box::new(Dummy{
                                            a:0,
                                            b:0
                                        });
    take(&res);
    res.a = 2048; // take函数返回不会释放 res所指向的资源,因为arg是只读借用,且res是资源的所用者
}

// res资源被 arg 只读借用
fn take(arg: &Box<Dummy>) {
    arg.a = 2048; // 编译错误: Cannot mutate via an immutable refrerence
}

 

语法学习:

只读借用: arg: &DataType

 

只读借用(&)

    允许存在多个对同一资源的只读借用,但是只读借用存在期间不允许对资源进行任何修改

    &运算符用来获取资源的只读借用

struct Dummy {a:i32, b:i32}

fn foo() {
    let mut res = Box::new(Dummy{a:0, b:0});
    {
        let alias1 = &res;
        let alias2 = &res;
        let alias3 = alias2;
        res.a = 2048; //编译报错:在大括号区间,存在对资源res的只读借用,不允许对资源进行修改
    }
    res.a = 2048;
}

可变借用(&mut)

struct Dummy {a:i32, b:i32}

fn foo() {
    let mut res = Box::new(Dummy{a:0, b:0});
    take(&mut res);
    res.a = 4096;
    let borrower = &mut res; // 可变借用
    let alias       = &mut res; // 编译报错:可变借用(borrower)对资源具有独占性, (在borrower生命周期内)不充许其它借用存在
}

fn take (arg: &mut Box<Dummy>) {
    arg.a = 2048;
}

可变性

    类似函数式语言,Rust中所有的资源都默认不可变

    需要使用 mut 关键字声明某个资源为可变的

struct Dummy {a:i32, b:i32}
fn foo() {
    let res = Box::new(Dummy{a:0, b:0});
    res.a = 2048; // 错误:资源是不可变的
    let borrower = &mut res; // 错误:不充许对不可变资源进行可变借用
}

生命周期

    编译器使用生命周期对代码逻辑进行限制:借用的存活时间不应该比被借用对象长,解决悬垂指针问题

    生命周期可以理解为变量作用域的长度,变量a比变量b 活得长 outlive 即指a的作用域能够涵盖b的作用域

    Rust编译器会尽量窄化变量的生命周期

let a = 0;
let y = &x;
let z = &y;

自动类型推导,自动声明周期推导,解开语法糖

//Note: `'a: {` and `&'b x` is not valid syntax!
'a: {
    let x : i32 = 0;
    'b: {
         //lifetime used is 'b because that's good enough.
        let y : &'b  i32 = &'b x;
        'c: {
            //ditto on 'c
            let z: &'c &'b i32 = &'c y;
        }
    }
}

拒绝悬垂指针

 

拒绝迭代器失效

c++无法阻止程序迈向段错误

std::vector<int> v{0,1,2};
int& x = &v[0];
v.push_back(3); // vector在push数据,原来的存储空间不足时会重新分配一个更大的空间,将旧数据复制到新的空间,这时 x 指向的空间就可能是非法的出现段错误
std::cout << x << std::endl;

Rust试图拯救迷途的程序员

let mut data = vec![0,1,2];
let x = &data[0]; //desuger let x : i32 = Index::index::(&data, 0);
data.push(3);     //desuger Vec::push(&mut data, 4)  编译报错,可变借用具有独占性,上面的那一行desuger之后用到了data的只读借用,不满足独占性,违反借用规则
println!("{}", x);
'a: { // 生命周期 'a
    let mut data: Vec<i32> = vec![1,2,3];
    'b: { //生命周期'b
        // 'b is as big as we need this borrow to be (just need to get to `println!`)
         let x: &'b i32 = Index::index::<'b>(&'b data, 0);
        'c: {
            //Temporary scope because we don't need the &mut to last any longer
            Vec::push(&'c mut data, 4);
        }
        println!("{}", x);
    }
}

借用 + 生命周期 = 痛苦之源

 

并发安全问题

    数据竞争导致的

泛型与Trait

    Rust使用Trait对类型的行为进行抽象

    组合优于继承

Trait的作用

    接口抽象

    泛型参数约束

    类型标签(Copy, Clone)

    抽象类型

trait Greeting {
    fn greet(&self);
}

struct Student;
struct Teacher;

impl Greeting for Student {
    fn greet(&self) { println!("I'm Student");}
}

impl Greeting for Teacher {
    fn greet(&self) {println!("I'm Teacher");}
}

fn greet<T: Greeting>(human: &T) {
    human.greet();
}

数据竞争

    Must be this tall to write multi-threaded code : Mozlia 公司的梗,多线程编程的难度

    数据竞争:线程安全的一生之敌

Rust中多线程

    多线程的相关内容位于std::thread模块中,可以直接使用spawn函数创建新的线程,spawn是对系统线程的封装

    假如我们在多个Rust线程访问一处资源

use std::thead
fn main() {
    let mut v: Vec<i32> = vec![];
    thread::spawn(|| {
        let l = v.len();
    });
    println!("{:?}", v);
}
//上面代码会编译报错
//Rust认为闭包具有静态生命周期与程序生命周期一样从生到死
//闭包会捕获外部变量来使用,该例子中闭包作为静态生命周期
//使用到 main 函数中 变量 v 的引用,团包认为 v 是 main 的局部变量
//v的生命周期没有静态生命周期长或一样

    closure may outlive the current function, but it borrows 'v', which is owned by the current function

添加move关键字,转移v的所有权,至团包中

use std::thread

fn main() {
    let mut v: Vec<i32> = vec![];
    thread::spawn(move || {
        let l = v.len();
    });
    println!("{:?}", v); //编译时会报错 v 的所有权已移至闭包中 不能再使用 v
}
//使用move关键字,将v的包有权移至闭包中
//

无畏并发

    我们没有办法在多线程中直接使用普通的共享变量

    大道至简:Rust创造性地使用类型系统来确保多线程程序中不存在数据竞争

两个特殊的Trait

    std::marker::Send 

            如果类型T实现了Send类型,那说明这个类型的变量在不同线程中传递所有权是安全的

            几乎所有的Rust类型都是Send,引用计数Rc<T>类型除外,只能用于单线程场景(为了零成本抽象)

            若没有实现Send类型,若试图在线程间发送,编译时会报the trait Send is not implemented for ... 错误

    std::marker::Sync

             如果类型T实现了Sync,那说明在不同的线程中使用&T访问同一个变量是安全的

            对于任意类型T,如果&T是Send的话T就是Sync的

            基本类型是Sync的,安全由Sync的类型组成的类型也是Sync的,Rc<T>, Cell<T>, RefCell<T> 也不是Sync的,但是Mutex<T>是Sync的

    不正确的实现 Send 和 Sync 会导致未定义的行为

//只要使用Rust提供的线程安全相关的设施,便能够安全地实现线程间状态共享
//最终实现版本
use std::thread;
use std::sync::{Mutex, Arc}; // Mutex 复制锁 Arc 原子引用计数器实现Send语义

fn main() {
    let arc1 = Arc::new(Mutex::new(vec![]));
    let arc2 = arc1.clone();
    let t = thread::spawn(move || {
        let mut v = arc2.lock().unwrap();
        v.push(1);
    });
    t.join();
    let v = arc1.lock().unwrap();
    println!("{:?}", v);
}

双向链表困境

    如何激怒一位Rust语言爱好者:让TA实现双向链表

//版本1
struct Node<T> {
    data: T,
    prev: Box<Node<T>>,
    next: Box<Node<T>>,
}

//版本2
struct Node<T> {
    data: T,
    prev: Rc<Node<T>>,
    next: Rc<Node<T>>,
}

//版本3
struct Node<T> {
    data: T,
    prev: Weak<Node<T>>,
    next: Rc<Node<T>>,
}

//版本4
struct Node<T> {
    data: T,
    prev: Option<Weak<Node<T>>>,
    next: Option<Rc<Node<T>>>,
}

//版本5
struct Node<T> {
    data: T,
    prev: Option<Weak<RefCell<Node<T>>>>,
    next: Option<Rc<RefCell<Node<T>>>>,
}

  Rust的解决方案: Unsafe

       最小化unsafe代码块的范围

      对unsafe代码需要进行更多思考及评审

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值