rust学习-智能指针

这篇具有很好参考价值的文章主要介绍了rust学习-智能指针。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

适用场景

有一个在编译时未知大小的类型,想在需要确切大小的上下文使用该类型值

示例1

无意义的例子:将一个单独的值存放在堆上并不是很有意义,b更应该放到栈上

fn main() {
    let b = Box::new(5);
    // box 在 main 的末尾离开作用域时,它将被释放
    // 释放过程作用于 box 本身(位于栈上)和它所指向的数据(位于堆上)
    println!("b = {}", b);
}

示例2-递归类型

一种无法在编译时知道大小的类型是 递归类型(recursive type)
其值的一部分可以是相同类型的另一个值

递归类型来源于Lisp语言:cons 函数(“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表,即构建一个新的容器而将 x 的元素放在新容器的开头,其后则是容器 y 的元素

最简单直接的智能指针是 box,其类型是 Box
box 允许将一个值放在堆上而不是栈上,留在栈上的则是指向堆数据的指针
box 只提供了间接存储和堆分配;没有任何其他特殊的功能
Box 类型是一个智能指针,因为它实现了 Deref trait,它允许 Box 值被当作引用对待
Box 值离开作用域时,由于 Box 类型 Drop trait 的实现,box 所指向的堆数据也会被清除

enum List {
    // 编译失败
    // recursive type has infinite size
    Cons(i32, List),
    Nil,
}

use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

此时Cons的大小
rust学习-智能指针,rust,rust,学习,开发语言
改为Box

// 不加这个  println!("list={:?}", list) 就会编译失败
#[derive(Debug)]

// 这里的List是自定义的enum,不是crate::list
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};
// 等价于
// use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));
    println!("list={:?}", list)
}

此时Cons的大小
rust学习-智能指针,rust,rust,学习,开发语言

有大量数据并希望在确保数据不被拷贝的情况下转移所有权

转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。
为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。
接着,只有少量的指针数据在栈上被拷贝

拥有一个值并只关心它的类型是否实现特定 trait 而不是其具体类型

trait 对象

Deref trait 将智能指针当作常规引用处理

解引用强制转换将一种类型(A)隐式转换为另外一种类型(B)的引用
因为 A 类型实现了 Deref trait,并且其关联类型是 B 类型
这些解析都发生在编译时,所以利用解引用强制转换并没有运行时损耗

解引用强制转换可以将 &String 转换为 &str
因为类型 String 实现了 Deref trait 并且其关联类型是 str

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

第一个示例

fn main() {
    let x = 5;
    let y = &x;
    assert_eq!(5, x);
    assert_eq!(5, *y);
    // 如下执行出错
    //  assert_eq!(5, y); // 必须使用 *y 来解出引用所指向的值
    
    let x = 5;
    let y = Box::new(x);
    assert_eq!(5, x);
    assert_eq!(5, *y);
}

第二个示例

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

// 不为MyBox实现Deref Trait,将编译失败
// 没有 Deref trait 的话,编译器只会解引用 & 引用类型
// deref 方法向编译器提供了获取任何实现了 Deref trait 的类型的值
// 调用这个类型的 deref 方法来获取一个它知道如何处理解引用
// 比如执行 *y,其实就是*(y.deref())
// 这个特性可以写出行为一致的代码,无论是面对的是常规引用还是实现了 Deref 的类型
impl<T> Deref for MyBox<T> {
    type Target = T; // 用于此 trait 的关联类型,关联类型是一个稍有不同的定义泛型参数的方式

    fn deref(&self) -> &T {
    	// 如果 deref 方法直接返回值而不是值的引用,其值(的所有权)将被移出 self
    	// 并不希望获取 MyBox<T> 内部值的所有权
    	// * 运算符都被替换成了先调用 deref 方法再接着使用 * 解引用的操作,且只会发生一次
        &self.0
    }
}

当将特定类型的值的引用作为参数传递给函数或方法,但是被传递的值的引用与函数或方法中定义的参数类型不匹配时,会发生解引用强制转换。
这时会有一系列的 deref 方法被调用,把提供的参数类型转换成函数或方法需要的参数类型。

fn hello(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    let m = MyBox::new(String::from("Rust")); // rust_demo::MyBox<alloc::string::String>
    // &m 调用 hello 函数,其为 MyBox<String> 值的引用
    // MyBox<T> 上实现了 Deref trait,Rust 可以通过 deref 调用将 &MyBox<String> 变为 &String
    // 标准库提供了 String 上的 Deref 实现,其会返回字符串 slice
    // Rust 再次调用 deref 将 &String 变为 &str
    print_type_of(&m);
    hello(&m);
}

// 如果改用如下,则也可以,但是hello中的传参就很麻烦
fn main() {
let m = MyBox::new(String::from(“Rust”));
hello(&(*m)[…]);
}

解引用强制转换如何与可变性交互

(1)当 T: Deref<Target=U> 时从 &T 到 &U
如果有一个 &T,而 T 实现了返回 U 类型的 Deref,则可以直接得到 &U
(2)当 T: DerefMut<Target=U> 时从 &mut T 到 &mut U
(3)当 T: Deref<Target=U> 时从 &mut T 到 &U
Rust 也会将可变引用强转为不可变引用。反之不可能

析构函数-使用 Drop Trait 运行清理代码

智能指针上下文中讨论 Drop 是因为其功能几乎总是用于实现智能指针
Box 自定义了 Drop 用来释放 box 所指向的堆空间

Rust 并不允许主动调用 Drop trait 的 drop 方法,防止double free;
当希望在作用域结束之前就强制释放变量的话,应该使用由标准库提供的 std::mem::drop

Drop trait 实现中指定的代码可以用于许多方面,来使得清理变得方便和安全
比如可以用其创建自己的内存分配器!通过 Drop trait 和 Rust 所有权系统,无需担心代码清理

// Drop trait 包含在 prelude 中,所以无需导入它
// 通常需要指定类型所需执行的清理代码而不是打印信息,这里展只是例子
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("my stuff") };
    let d = CustomSmartPointer { data: String::from("other stuff") };
    println!("CustomSmartPointers created.");
}

// 打印如下:
// CustomSmartPointers created.
// Dropping CustomSmartPointer with data `other stuff`!
// Dropping CustomSmartPointer with data `my stuff`!

有时可能需要提早清理某个值。
比如当使用智能指针管理锁时;可能希望强制运行 drop 方法来释放锁
以便作用域中的其他代码可以获取锁

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("some data") };
    println!("CustomSmartPointer created.");
    drop(c);
    
	// 如果执行 c.drop(),则报错,不允许double free
    println!("CustomSmartPointer dropped before the end of main.");
}

// 打印结果如下:
// CustomSmartPointer created.
// Dropping CustomSmartPointer with data `some data`!
// CustomSmartPointer dropped before the end of main.

Rc 引用计数智能指针

Rc 用于当我们希望在堆上分配一些内存供程序的多个部分读取,
而且无法在编译时确定程序的哪一部分会最后结束使用它的时候。
如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,
正常的所有权规则就可以在编译时生效。

比如创建两个共享第三个列表所有权的列表
rust学习-智能指针,rust,rust,学习,开发语言

错误案例

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
	// Cons 成员拥有其储存的数据
    let a = Cons(5,
        Box::new(Cons(10,
            Box::new(Nil))));
    // 当创建 b 列表时,a 被移动进了 b 这样 b 就拥有了 a
    let b = Cons(3, Box::new(a)); 
    let c = Cons(4, Box::new(a)); // 这里报错 use of moved value: `a`
}

Rc 只能用于单线程场景

读取类型

#[derive(Debug)]

enum List {
	// 每一个 Cons 变量都包含一个值和一个指向 List 的 Rc<T>
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5,
		Rc::new(Cons(10,
		    Rc::new(Nil)))));
    print_type_of(&a); // a的类型是 alloc::rc::Rc<rust_demo::List>
    let b = Rc::clone(&a);
    print_type_of(&b); // b的类型是 alloc::rc::Rc<rust_demo::List>
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>());
}

正常案例

#[derive(Debug)]

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5,
		Rc::new(Cons(10,
		    Rc::new(Nil)))));
	// 每次调用 Rc::clone,Rc<List> 中数据的引用计数都会增加
	// 直到有零个引用之前其数据都不会被清理
	// 也可以调用 a.clone(),不过Rust 的习惯是使用 Rc::clone
	// Rc::clone 只会增加引用计数,不会深拷贝
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
    println!("a={:?}", a);
    println!("b={:?}", b);
    println!("c={:?}", c);
}

// 打印
// a=Cons(5, Cons(10, Nil))
// b=Cons(3, Cons(5, Cons(10, Nil)))
// c=Cons(4, Cons(5, Cons(10, Nil)))

// 使用a.clone()示例
// let b = Cons(3, a.clone());
// let c = Cons(4, a.clone());

观察引用计数

调用 Rc::strong_count 函数获得

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

通过不可变引用, Rc 允许在程序的多个部分之间只读地共享数据

RefCell

为什么 RefCell 不同于 Box ?

(1)在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 之一(而不是两者)
(2)引用必须总是有效

对于引用和 Box,借用规则的不可变性作用于编译时,如果违反规则,得到一个编译错误
对于 RefCell,借用规则的不可变性作用于运行时。如果违反规则,程序会 panic 并退出

在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许的,比如停机问题(Halting Problem)
RefCell 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。

Rc,RefCell 只能用于单线程场景

Box,Rc,RefCell 的适用场景

Rc 允许相同数据有多个所有者;Box 和 RefCell 有单一所有者
Box 允许在编译时执行不可变或可变借用检查
Rc仅允许在编译时执行不可变借用检查
RefCell 允许在运行时执行不可变或可变借用检查,即便 RefCell 自身不可变,仍可以修改其内部值。
在不可变值内部改变值就是内部可变性模式

错误案例

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
    where T: Messenger {
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
             self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger.send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Mock结构体
    struct MockMessenger {
        sent_messages: Vec<String>,
    }
    
    // Mock结构体的方法
    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger { sent_messages: vec![] }
        }
    }

	// Mock结构体实现 Messenger Trait
    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            // cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
            // `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
            // send 方法获取了 self 的不可变引用
            self.sent_messages.push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.len(), 1);
    }
}

正常案例

borrow 方法返回 Ref 类型的智能指针
borrow_mut 方法返回 RefMut 类型的智能指针
这两个类型都实现了 Deref,所以可以当作常规引用对待

RefCell 记录当前有多少个活动的 Ref 和 RefMut 智能指针
每次调用 borrow,RefCell 将活动的不可变借用计数加一
当 Ref 值离开作用域时,不可变借用计数减一
RefCell 在任何时候只允许有多个不可变借用或一个可变借用

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
    where T: Messenger {
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
             self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger.send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger { sent_messages: RefCell::new(vec![]) }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
        	// 调用 self.sent_messages 中 RefCell 的 borrow_mut 方法来获取 RefCell 中值的可变引用
        	// 对 vector 的可变引用调用 push 以便记录测试过程中看到的消息
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);
        
        // 调用 RefCell 的 borrow 以获取 vector 的不可变引用
	    assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}

不符合借用规则的RefCell

RefCell 在任何时候只允许有多个不可变借用或一个可变借用,该示例中有多个可变借用

【缺点】
在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误
甚至有可能发布到生产环境才发现
还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚

【优点】
使用 RefCell 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能
虽然有取舍,但是可以选择使用 RefCell 来获得比常规引用所能提供的更多的功能

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
    where T: Messenger {
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
             self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger.send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger { sent_messages: RefCell::new(vec![]) }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            // 在相同作用域中创建两个可变引用,不允许
            let mut one_borrow = self.sent_messages.borrow_mut();
            // panicked at 'already borrowed
        	let mut two_borrow = self.sent_messages.borrow_mut();

        	one_borrow.push(String::from(message));
        	two_borrow.push(String::from(message));
	    }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

	assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}

---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread ‘tests::it_sends_an_over_75_percent_warning_message’ panicked at ‘already borrowed: BorrowMutError’, src/lib.rs:54:53

Rc + RefCell:拥有多个可变数据所有者

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    // Rc::new(RefCell::new(5)) 储存在变量 value 中以便之后直接访问
    let value = Rc::new(RefCell::new(5));

    // 将列表 a 封装进了 Rc<T> 这样当创建列表 b 和 c 时,他们都可以引用 a
    // alloc::rc::Rc<rust_demo::List>
    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
    print_type_of(&a);
    // rust_demo::List
    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
     print_type_of(&b); // rust_demo::List
     // rust_demo::List
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
     print_type_of(&c); // rust_demo::List
    println!("a before = {:?}", a);
    println!("b before = {:?}", b);
    println!("c before = {:?}", c);

    // borrow_mut 方法返回 RefMut<T> 智能指针,可以对其使用解引用运算符并修改其内部值
    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>());
}

// a before = Cons(RefCell { value: 5 }, Nil)
// b before = Cons(RefCell { value: 6 }, Cons(RefCell { value: 5 }, Nil))
// c before = Cons(RefCell { value: 10 }, Cons(RefCell { value: 5 }, Nil))
//
// a after = Cons(RefCell { value: 15 }, Nil)
// b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
// c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))

可以拥有一个表面上不可变的 List
可以使用 RefCell 中提供内部可变性的方法来在需要时修改数据

循环引用

Rust很难产生内存泄漏,但也不是不可能

糟糕案例

rust学习-智能指针,rust,rust,学习,开发语言

use std::rc::Rc;
use std::cell::RefCell;
use crate::List::{Cons, Nil};

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    // 修改 Cons 成员所指向的 List
    // 在有 Cons 成员的时候访问其第二项
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    // a的类型:alloc::rc::Rc<rust_demo::List>
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
    // a的计数是1
    println!("a initial rc count = {}", Rc::strong_count(&a));
    // a的tail值:Some(RefCell { value: Nil })
    println!("a next item = {:?}", a.tail());

    // b的类型:alloc::rc::Rc<rust_demo::List>
    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
    // a的计数是2
    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    // b的计数是1
    println!("b initial rc count = {}", Rc::strong_count(&b));
    // b的tail值:Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
    println!("b next item = {:?}", b.tail());

    // 两个循环引用互相引用
    // 处理只匹配一个模式的值而忽略其他模式的情况
    // 使用 tail 方法获取 a 中 RefCell<Rc<List>> 的引用
    if let Some(link) = a.tail() {
        // RefCell 的 borrow_mut 方法将其值从存放 Nil 的 Rc<List> 修改为 b 中的 Rc<List>
        *link.borrow_mut() = Rc::clone(&b);
    }
    //  上述3行如果改为如下写法
    //     let aTailInfo = a.tail();
	//     match aTailInfo {
	//       Some(link) => {
	//          // 使用 tail 方法获取 a 中 RefCell<Rc<List>> 的引用
	//		    print_type_of(&link);           // &core::cell::RefCell<alloc::rc::Rc<rust_demo::List>>
	//		    println!("a tail is {:?}", link); // RefCell { value: Nil }
	//          // 这里要加mut,不然编译 *innerInfo = bClone失败
	//          let mut innerInfo = list.borrow_mut();
    //          print_type_of(&innerInfo); // core::cell::RefMut<alloc::rc::Rc<rust_demo::List>>
    //          let bClone = Rc::clone(&b);
	//          print_type_of(&bClone); // alloc::rc::Rc<rust_demo::List>
	//         *innerInfo = bClone
	//	     }
	//		  _ => {
	//	            println!("a tail is Nil"); // 不会走到这里
	//		  }
    //      }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // 死循环发生在此处,直至栈溢出
    // println!("a next item = {:?}", a.tail());
}

解决办法

CI、MR

使用自动化测试、代码评审和其他软件开发最佳实践来使其最小化

强弱隐引用

调用 Rc::clone 会增加 Rc 实例的 strong_count
只在其 strong_count 为 0 时才会被清理的 Rc 实例
调用 Rc::downgrade 并传递 Rc 实例的引用来创建其值的 弱引用(weak reference)
调用 Rc::downgrade 会将 weak_count 加1
Rc 类型使用 weak_count 来记录其存在多少个 Weak 引用
weak_count 无需计数为 0 就能使 Rc 实例被清理

Weak 引用的值可能已经被丢弃
为了使用 Weak 所指向的值,必须确保其值仍然有效。为此可以调用 Weak 实例的 upgrade 方法,这会返回 Option<Rc>
如果 Rc 值还未被丢弃,则结果是 Some;如果 Rc 已被丢弃,则结果是 None

返回一个 Option,就可以确保 Rust的使用者 会处理 Some 和 None 的情况,所以它不会返回非法指针,牛啊

弱引用并不属于所有权关系
不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    // 如果父节点被丢弃了,其子节点也应该被丢弃
    // 然而子节点不应该拥有其父节点:如果丢弃子节点,其父节点应该依然存在
    // 父节点是弱引用
    // 避免循环引用导致的内存泄漏
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    // 尝试使用 upgrade 方法获取 leaf 的父节点引用时,会得到一个 None 值
    // 没有父节点时,提权获取到的值为None
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); // leaf parent = None

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
    // 使用 Rc::downgrade 函数从 branch 中的 Rc<Node> 值创建了一个指向 branch 的 Weak<Node> 引用
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    // 有父节点时,弱指针提权成功
    // leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

引用计数可视化
管理计数和值的逻辑都内建于 Rc 和 Weak 以及它们的 Drop trait 实现中

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    // 此时叶子节点的强引用为1,弱引用为0
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]), // 一开始初始化时就指向子节点,Rc::clone增加leaf的强引用计数
        });
        
       // 父节点的强引用计数为1, 弱引用计数为0
        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        // 子节点指向父节点
        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

		// 父节点的强引用计数为1, 弱引用计数也为1(被叶子节点引用了)
        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        // 此时叶子节点的强引用是2,弱引用是0
        // branch 的 branch.children 中储存了 leaf 的 Rc<Node> 的拷贝,不过弱引用计数仍然为 0
        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
        
		// 当内部作用域结束时,branch 离开作用域,Rc<Node> 的强引用计数减少为 0,所以其 Node 被丢弃
		// 来自 leaf.parent 的弱引用计数 1 与 Node 是否被丢弃无关,所以并没有产生任何内存泄漏
		// 对leaf节点不产生任何影响
    }
    
    // 此时叶子节点的强引用依旧是1,弱引用依旧是0
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());  // leaf parent = None
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}

Cell

类似 RefCell 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 Cell

Mutex

提供线程间安全的内部可变性

总结

使用智能指针来做出不同于 Rust 常规引用默认所提供的保证与取舍
Box 有一个已知的大小并指向分配在堆上的数据
Rc 记录了堆上数据的引用数量以便可以拥有多个所有者
RefCell 和其内部可变性提供了一个可以用于当需要不可变类型但是需要改变其内部值能力的类型,并在运行时而不是编译时检查借用规则文章来源地址https://www.toymoban.com/news/detail-602975.html

附录

list学习

use list::List;

fn main() {
    let mut list = List::new();
    // Check empty list behaves right
    assert_eq!(list.pop(), None);

    // Populate list
    list.push(1);
    list.push(2);
    list.push(3);

    // Check normal removal
    assert_eq!(list.pop(), Some(3));
    assert_eq!(list.pop(), Some(2));

    // Push some more just to make sure nothing's corrupted
    list.push(4);
    list.push(5);

    // Check normal removal
    assert_eq!(list.pop(), Some(5));
    assert_eq!(list.pop(), Some(4));

    // Check exhaustion
    assert_eq!(list.pop(), Some(1));
    assert_eq!(list.pop(), None);
}
cat Cargo.toml
[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
list = "~0.1.3"

到了这里,关于rust学习-智能指针的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust- 智能指针

    A smart pointer is a data structure that not only acts like a pointer but provides additional functionality. This “smartness” comes from the fact that smart pointers encapsulate additional logical or semantic rules, which are automatically applied to simplify memory or resource management tasks. While different programming languages implement smart point

    2024年02月14日
    浏览(45)
  • rust 智能指针

    Rust中基本数据类型(如整数、浮点数、布尔值等)通常存储在栈上。而动态分配的数据,如 BoxT 和 VecT 等,存储在堆上。 Rust 中 Box 是一种智能指针类型,通常用于将值放置在堆上而不是栈上。在 Rust 中,由于所有值的默认生命周期都在当前作用域结束时结束,因此在情况需

    2024年02月06日
    浏览(90)
  • 【Rust 基础篇】Rust Rc 智能指针的使用

    在 Rust 中,Rc(Reference Counting)是一种智能指针,用于实现多所有权共享数据的引用计数。Rc 智能指针允许多个所有者共享相同的数据,并在没有任何所有者时自动释放数据。 本篇博客将详细介绍 Rust 中 Rc 智能指针的使用方法和相关概念,以及它在代码中的应用场景。 Rc 智

    2024年02月16日
    浏览(42)
  • 【跟小嘉学 Rust 编程】十五、智能指针

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月11日
    浏览(58)
  • Rust语言中级教程之指针

    指针是计算机引用无法立即直接访问的数据的一种方式(类比 书的目录) 数据在物理内存(RAM)中是分散的存储着 地址空间是检索系统 指针就被编码为内存地址,使用 usize 类型的整数表示。 一个地址就会指向地址空间中的某个地方 地址空间的范围是 OS 和 CPU 提供的外观界

    2024年02月02日
    浏览(35)
  • 【Rust 基础篇】Rust 的 `Rc<RefCell<T>>` - 共享可变性的智能指针

    在 Rust 中, RcRefCellT 是一种组合智能指针,用于实现多所有权共享可变数据。 Rc 允许多个所有者共享相同的数据,而 RefCell 允许在有多个引用的情况下对数据进行可变操作。 本篇博客将详细介绍 Rust 中 RcRefCellT 的使用方法和相关概念,以及它在代码中的应用场景。 RcRefCell

    2024年02月16日
    浏览(41)
  • 【Rust】——通过Deref trait将智能指针当作常规引用处理

    💻博主现有专栏:                 C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等 🥏主页链接:     

    2024年04月26日
    浏览(37)
  • rust 自动化测试、迭代器与闭包、智能指针、无畏并发

    编写测试可以让我们的代码在后续迭代过程中不出现功能性缺陷问题;理解迭代器、闭包的函数式编程特性; BoxT 智能指针在堆上存储数据, RcT 智能指针开启多所有权模式等;理解并发,如何安全的使用线程,共享数据。 编写测试以方便我们在后续的迭代过程中,不会改坏

    2024年02月16日
    浏览(40)
  • Rust踩雷笔记(5)——刷点链表的题(涉及智能指针Box,持续更新)

    只能说Rust链表题的画风和C++完全不一样,作为新手一时间还不太适应,于是单独为链表、智能指针开一篇,主要记录leetcode相关题型的答案以及注意事项。 🍑关键操作 as_ref() 将 OptionT 、 OptionT 或者 mut OptionT 转换为 OptionT as_mut() 将 OptionT 、 mut OptionT 转换为 Optionmut T ,不能对

    2024年02月11日
    浏览(33)
  • C/C++|物联网开发入门+项目实战|指针|嵌入式C语言高级|C语言内存空间的使用-学习笔记(9)

    参考: 麦子学院-嵌入式C语言高级-内存空间 内存类型资源地址、门牌号的代名词 指针:地址的代名词 指针变量:存放指针这个概念的盒子 *P char *p *p; C语言娟译器对指针这个特殊的概念,有2个疑问? 1、分配一个盒子,盒子要多大? 在32bit系统中,指针就4个字节 2、盘子里存放

    2023年04月22日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包