首页 > 其他分享 >Rust里面的内部可变性

Rust里面的内部可变性

时间:2025-01-01 14:51:48浏览次数:1  
标签:里面 s1 ptr println let push 可变性 hello 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>>

标签:里面,s1,ptr,println,let,push,可变性,hello,Rust
From: https://www.cnblogs.com/dayq/p/18645935

相关文章

  • 【Rust自学】9.2. Result枚举与可恢复的错误 Pt.1:match、expect和unwrap处理错误
    喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)9.2.1.Result枚举通常情况下,错误都没有严重到需要停止整个程序的地步。某个函数之所以运行失败或者是遇到错误通常是由一些可以简单解释并做出响应的原因引起的。比......
  • spring boot迁移计划 第Ⅰ章 --chapter 1. rust hyper 结合rust nacos-client开发naco
    1.toml依赖nacos_rust_client="0.3"local_ipaddress="0.1"2.代码//todo维护实时服务列表,用来在请求到来时选择转发至具体的服务usestd::sync::Arc;uselog::debug;usenacos_rust_client::client::{naming_client::{Instance,InstanceDefaultList......
  • spring boot迁移计划 第Ⅰ章 --chapter 1. rust hyper 结合rust nacos-client开发naco
    1.toml依赖toml="0.8"2.代码由于项目还未完成,部分配置(如数据库等)还未增加,后续更新增加uselog::info;useserde::Deserialize;usestd::{fs,sync::LazyLock};usecrate::init::constant::*;//创建全局静态配置文件staticCONFIG:LazyLock<Config>=LazyL......
  • spring boot迁移计划 第Ⅰ章 --chapter 1. rust hyper 结合rust nacos-client开发na
    1.toml依赖hyper={version="1",features=["full"]}tokio={version="1",features=["full"]}http-body-util="0.1"hyper-util={version="0.1",features=["full"]}2.......
  • Rust f64详解
    一、Rust中的f64类型与IEEE754双精度浮点数Rust中的f64类型是一个双精度浮点数类型,它严格遵循IEEE754双精度标准。这意味着f64类型在Rust中的存储和表示方式与IEEE754双精度浮点数完全一致。二、存储格式f64类型由64位二进制数表示,分为以下三部分:1.符号位(1位):位置:第......
  • rust学习十五.3、智能指针相关的Deref和Drop特质
     一、前言智能指针为什么称为智能指针?大概因为它所包含的额外功能。这些额外的功能使得编码或者运行时让指针看起来更有效、并体现某些“智”的特征,所以,我猜测这应该是rust发明人这么称呼此类对象为智能的原因。 据前面有关章节所述,我们知道智能指针多基于结构体(struct)扩......
  • [rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(00):iced简单窗口的实现以及在窗口显
    前言本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发。iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动。本合集是基于新版本的关于分部件(widget)的使用介绍,包括源代码介绍、实例使用等。环境配置系统:window10平台:visualstudiocode语言:rust......
  • C语言里面的size_t是什么意思
    size_t是C语言中一个非常常用的数据类型,主要用于表示对象的大小或计数。它定义在<stddef.h>、<stdio.h>、<stdlib.h>等标准头文件中。以下是对size_t的详细解释:size_t的定义和用途定义:size_t是一个无符号整数类型(typedef定义),其具体大小依赖于编译器和平台。通......
  • 【Rust自学】8.6. HashMap Pt.2:更新HashMap
    8.6.0.本章内容第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们......
  • 【Rust自学】8.4. String类型 Pt.2:字节、标量值、字形簇以及字符串的各类操作
    8.4.0.本章内容第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们......