Rust里面的内部可变性

1.Cell

use std::cell::Cell;
#[derive(Debug)]
struct SomeStruct {
    regular_field: u8,
    special_field: Cell<u8>,
}
fn main() {
    let my_struct = SomeStruct {
        regular_field: 0,
        special_field: Cell::new(1),
    };
    println!("{:?}",my_struct);
    my_struct.special_field.set(17);
    println!("{:?}",my_struct);
}
输出:
SomeStruct { regular_field: 0, special_field: Cell { value: 1 } }
SomeStruct { regular_field: 0, special_field: Cell { value: 17 } }
官方的例子简单明了,somestruct实现了内部可变性,因为my_struct没有使用mut修饰,所有refular_field不能直接赋值修改,但是special_filed却可以通过Cell结构的方法进行修改,组要注意的是Cell<T>的T需要实现Copy语义。
#[derive(Debug)]
struct other_struct {
    ss:Cell<String>,	//error[E0277]: the trait bound `String: Copy` is not satisfied
}
let o = other_struct {ss:Cell::new("hello".into())};
println!("{:?}",o);
// o.ss.set("world".into());
println!("{:?}",o);
没有实现Copy语义,或许可以定义这个结构体,但是具体使用结构体就会编译错误。

2.RefCell

#[derive(Debug)]
struct ref_struct {
    num:i32,
    rs: RefCell<String>,
}
fn main() {
    let r = ref_struct {rs:RefCell::new("hello".into()),num:66};
    println!("{:?}",r);
    {
        let mut rr = r.rs.borrow_mut();
        rr.push_str(" oworld");
    }
    println!("{:?}",r.rs.borrow());
}
输出:
ref_struct { num: 66, rs: RefCell { value: "hello" } }
"hello world"
这个很直观了,和Cell相比,RefCell不限制RefCell<T>,T的类型。通常使用较多。

3.Rc

#[derive(Debug)]
struct struct1 {
    s:String,
}
fn main() {
    let mut one = struct1 {s:"hello".into()};
    println!("{:?}",one);
    let a1 = &mut one;
    a1.s.push_str(" world");
    // println!("{:?}",one);
    a1.s.push_str("!");
    println!("{:?}",one);
}
倘若我们将注释去掉,直接就会报编译错误,cannot borrow `one` as immutable because it is also borrowed as mutable,因为a1这个可变引用还很活跃。

但是,我们使用Rc和RefCell配合,就可以通过编译了。
#[derive(Debug)]
struct struct2 {
    s:Rc<RefCell<String>>,
}
fn main() {
    let one = struct2 {s:Rc::new(RefCell::new("hello".into()))};
    println!("{:?}",one);
    let mut a1 = one.s.borrow_mut();
    a1.push_str(" world");
    let a2 = one.s.borrow();
    println!("{:?}",a2);
    a1.push('!');
    println!("{:?}",one);
}
/*
struct2 { s: RefCell { value: "hello" } }
thread 'main' panicked at D:\code\leetcode\first_class\a1.rs:18:20:
already mutably borrowed: BorrowError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
*/
虽然,通过了编译,但是运行时还是会检查可变借用和不变借用,所以直接panic了,但是通过了编译

单独使用Rc的话,可以看作其他语言的共享所有权。
let s = String::from("hello");
println!("{:?}",s);
let s1 = s;
println!("{:?}",s1);
// println!("{:?}",s);  //^ value borrowed here after move
String是没有实现Copy语言的,所有s1=s就会转移所有权,但如果使用clone的话,就不是指向同一片内存了。
这时候可以使用Rc<String>
let s = Rc::new(String::from("hello"));
let s1 = Rc::clone(&s);
println!("{:?}",s1);
unsafe {
    let s_ptr = Rc::as_ptr(&s1) as *mut String;
    (*s_ptr).push_str(" world");
}
println!("{:?}",s1);
println!("{:?}",s);
输出如下:
"hello"
"hello world"
"hello world"
可见,二者指向同一片内存。

4.Arc

Arc和Rc的区别就在于,Arc可以跨线程使用,它的引用计数是原子引用计数。所以可以确保对引用计数的修改是安全的,因此可以用于多线程。也就是说,假设多个线程拥有Rc的clone,它们同时结束,会同时修改主线程的Rc计数,这是不安全的。
let s1 = Arc::new(String::from("hello"));
    let (sender, receiver) = std::sync::mpsc::sync_channel(0);//同步消息队列,确保两个线程同时运行到unsafe
    // let s2 = Arc::clone(&s1);    //两种方式clone都可以
    let s2 = s1.clone();

    let t1 = thread::spawn(move || {
        receiver.recv().unwrap();
        unsafe {
            let s_ptr = Arc::as_ptr(&s2) as *mut String;
            (*s_ptr).push_str(" thread");
        }
        println!("{:?}",s2);
    });
    println!("{:?}",s1);
    sender.send(()).unwrap();
    unsafe {
        let s_ptr = Arc::as_ptr(&s1) as *mut String;
        (*s_ptr).push_str(" main");
    }
    println!("{:?}",s1);
    t1.join().unwrap();
//输出如下:
"hello"
"hello main"
"hello main thread"
但也有概率hello thread main
不管顺序如何,至少跨线程共享了一段内存,不考虑写的安全性,都可以读写。
此外,不使用裸指针的话,还没有办法直接s1.push_str("")
help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<String>`

如果是下面这样的话,就很明显了,出现了争用内存的问题,导致读写脏数据

let s1 = Arc::new(String::from("hello"));
    let (sender, receiver) = std::sync::mpsc::sync_channel(0);
    // let s2 = Arc::clone(&s1);    //两种方式clone都可以
    let s2 = s1.clone();

    let t1 = thread::spawn(move || {
        receiver.recv().unwrap();
        unsafe {
            let s_ptr = Arc::as_ptr(&s2) as *mut String;
            (*s_ptr).push('t');
            (*s_ptr).push('h');
            (*s_ptr).push('r');
            (*s_ptr).push('e');
            (*s_ptr).push('a');
            (*s_ptr).push('d');
        }
        println!("thread push {:?}",s2);
    });
    println!("{:?}",s1);
    sender.send(()).unwrap();
    unsafe {
        let s_ptr = Arc::as_ptr(&s1) as *mut String;
        (*s_ptr).push('m');
        (*s_ptr).push('a');
        (*s_ptr).push('i');
        (*s_ptr).push('n');
    }
    println!("main push {:?}",s1);
    t1.join().unwrap();
    println!("last {:?}",s1);
//输出如下:
main push "hellothread"
thread push "hellothread"
last "hellothread"
push的main被完全覆盖掉了。

这时,就需要用到互斥锁了。

let s1 = Arc::new(Mutex::new(String::from("hello")));
    let (sender, receiver) = std::sync::mpsc::sync_channel(0);
    // let s2 = Arc::clone(&s1);    //两种方式clone都可以
    let s2 = s1.clone();

    let t1 = thread::spawn(move || {
        receiver.recv().unwrap();
        s2.lock().unwrap().push_str(" thread");
        println!("thread push {:?}",s2);
    });
    println!("{:?}",s1);
    sender.send(()).unwrap();
    s1.lock().unwrap().push_str(" main");
    println!("main push {:?}",s1);
    t1.join().unwrap();
    println!("last {:?}",s1);
//输出
Mutex { data: "hello", poisoned: false, .. }
main push Mutex { data: "hello main", poisoned: false, .. }
thread push Mutex { data: "hello main thread", poisoned: false, .. }
last Mutex { data: "hello main thread", poisoned: false, .. }
通过互斥锁,实现了跨线程读写同一片内存。使用起来就和数据结构直接读写一样,不需要裸指针

或者这样读写,也是安全的。
let t1 = thread::spawn(move || {
        receiver.recv().unwrap();
        // s2.lock().unwrap().push_str(" thread");
        {
            let mut m2 = s2.lock().unwrap();
            m2.push(' ');
            m2.push('t');
            m2.push('h');
            m2.push('r');
            m2.push('e');
            m2.push('a');
            m2.push('d');
        }
        println!("thread push {:?}",s2);
    });
    println!("{:?}",s1);
    sender.send(()).unwrap();
    // s1.lock().unwrap().push_str(" main");
    {
        let mut m1 = s1.lock().unwrap();
        m1.push(' ');
        m1.push('m');
        m1.push('a');
        m1.push('i');
        m1.push('n');
    }
//输出如下
Mutex { data: "hello", poisoned: false, .. }
main push Mutex { data: <locked>, poisoned: false, .. }
thread push Mutex { data: "hello main thread", poisoned: false, .. }
last Mutex { data: "hello main thread", poisoned: false, .. }

综上所述:

cell和refcell,可以实现结构体的内部可变性,分别是普通类型和所有类型。
Rc和Arc实现对同一片内存的共享所有权。但是要安全的修改,就只能在引用计数为1的时候。并且,rc用于不能跨线程,arc可以。
所以,Rc<RefCell<T>>用来实现单线程下对同一片内存的互斥修改,类似于读写锁。有可变借用就不能有不可变借用。运行时会检查借用安全。
Arc<RefCell<T>>却不能在多线程下使用,因为。 
the trait `Sync` is not implemented for `RefCell<String>`
所以,多线程下对同一片内存的互斥修改,需要使用Arc<Mutex<T>>
posted @   念秋  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示