rust学习-智能指针

news2024/11/25 3:29:00

适用场景

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

示例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的大小
在这里插入图片描述
改为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的大小
在这里插入图片描述

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

转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。
为了改善这种情况下的性能,可以通过 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 用于当我们希望在堆上分配一些内存供程序的多个部分读取,
而且无法在编译时确定程序的哪一部分会最后结束使用它的时候。
如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,
正常的所有权规则就可以在编译时生效。

比如创建两个共享第三个列表所有权的列表
在这里插入图片描述

错误案例

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很难产生内存泄漏,但也不是不可能

糟糕案例

在这里插入图片描述

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 和其内部可变性提供了一个可以用于当需要不可变类型但是需要改变其内部值能力的类型,并在运行时而不是编译时检查借用规则

附录

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"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R语言 PCA筛选变量

#PCA-筛选变量 X <- data[,2:415] pca <- prcomp(X, center TRUE, scale. TRUE) # 进行主成分分析 summary(pca) # 查看各个主成分的解释方差比例 library(factoextra) #碎石图依赖-fviz fviz_eig(pca,addlabelsT) #碎石图 X_selected <- pca$x[,1:20] # 选择前n个主…

pytorch实现图像投影变换

import cv2 import torchdef cpu_remap(numpy_img,mapx,mapy):return cv2.remap(numpy_img,mapx,mapy,cv2.INTER_LINEAR)def gpu_remap(numpy_img,map_tensor):numpy_img:原始图像格式为ndarraymap_tensor:[N,H,W,C]用于grid_sample的map参数&#xff0c;需要规制到-1到1# 准备…

opencv直方图

#include "iostream" #include "opencv2/opencv.hpp" using namespace std; using namespace cv;int main() {Mat img, gray;img imread("r4.jpg");cvtColor(img, gray, COLOR_BGR2GRAY);int nimages 1;//图片数量const int channels[] { 0 …

信捷PLC RC低通滤波器(C语言实现)

PLC信号处理系列之RC低通滤波器算法详细介绍请参考下面文章: PLC信号处理系列之一阶低通(RC)滤波器算法_plc滤波算法程序_RXXW_Dor的博客-CSDN博客1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1、在模拟常数要求较…

位域与共用体在通讯领域的应用

最近看到些代码&#xff0c;定义变量还能指定位宽&#xff0c;很有意思。像这样 unsigned int bit1 : 1;冒号 (&#x1f603; 后面的数字1表示变量的位宽或大小。 像这样的大小声明在低级编程和位操作中常被使用&#xff0c;以便精确控制变量的大小。 通讯协议解析用的多。 下面…

电脑安装双系统ubuntu18.04+windows后开机直接进入Windows解决方法

电脑型号&#xff1a;联想拯救者Y9000K2021H 系统&#xff1a;Windows11Ubuntu18.04双系统 问题&#xff1a;笔记本安装双系统后&#xff0c;Windows系统下处理word或者看论文&#xff1b;Ubuntu18.04系统安装ros进行机械臂控制等的研究。但最近开机后发现没有系统选项了&#…

【mars3d】将mars3d中的示例拷贝到自己项目中

mars3d 的功能示例 - 感觉做了很多的处理&#xff1b; 1、头部的按钮作用 重置和运行 - 这就是字面意思&#xff0c;都能理解哈&#xff1b; js - 顾名思义&#xff0c;js代码&#xff0c;也是我们可以改动的部分&#xff1b; 旁边那个 - 是vue部分&#xff0c;是不能修改的…

J2EE通用分页01

目录 一.总体思路 二.分页信息实体&#xff08;PageBean&#xff09; 三.后台分页数据查询 3.1 处理流程 流程图&#xff1a; 3.2 实现 Student实体&#xff0c;及对应的数据库表可自行准备 四.重构-提取公用方法 1.为了进行公共方法的抽取&#xff0c;需要找出上面实…

Transformer 模型实用介绍:BERT

动动发财的小手&#xff0c;点个赞吧&#xff01; 在 NLP 中&#xff0c;Transformer 模型架构是一场革命&#xff0c;极大地增强了理解和生成文本信息的能力。 在本教程[1]中&#xff0c;我们将深入研究 BERT&#xff08;一种著名的基于 Transformer 的模型&#xff09;&#…

uniapp app运行到ios详细流程

uniapp运行到IOS真机调试&#xff08;windows系统&#xff09; 工具步骤1.首先数据线连接电脑和手机2.右键点击桌面上的HBuilder&#xff0c;打开文件所在位置3.打开HBuilder编辑器里要运行的项目&#xff0c;点击运行>运行到手机或模拟器>运行到IOS APP基座>勾选你的…

【Java虚拟机学习2】HotSpot虚拟机下对象的创建及在Java堆中对象的内存分配、布局和对象的访问

HotSpot虚拟机下对象的创建及在Java堆中对象的内存分配、布局和对象的访问 一、对象的创建 Step1&#xff1a;类加载检查 虚拟机遇到一条new指令时&#xff0c;首先将检查是否能在常量池中定位到这个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载过、解…

【深度学习Week2】卷积神经网络

卷积神经网络 Convolutional Neural Networks&#xff0c;CNN 【第一部分&#xff1a;代码练习】1.MNIST 数据集分类2.CIFAR10 数据集分类3.使用 VGG16 对 CIFAR10 分类 【第二部分&#xff1a;问题总结】 【第一部分&#xff1a;代码练习】 1.MNIST 数据集分类 1.1 加载数据…

STM32入门学习之USART串口通信:

1.串口通信简介&#xff1a;通用异步收发传输器UART(Universal Asynchronous Receiver/Transmitter)是负责处理数据总线和串口之间的串/并通信的设备。UART通信规定了数据帧的格式&#xff1a;起始位、数据位、校验位、停止位等。UART异步通信只需要通信双方设置好数据帧的格式…

html2Canvas+JsPDF 导出pdf 无法显示网络图片

html2CanvasJsPDF 导出pdf 问题&#xff1a;类似于下面着这种网络图片使用img导出的时候是空白的 https://gimg3.baidu.com/search/srchttp%3A%2F%2Fpics4.baidu.com%2Ffeed%2F7e3e6709c93d70cf827fb2fda054500cb8a12bc9.jpeg%40f_auto%3Ftoken%3Dd97d3f0fd06e680e592584f8c7a2…

Devart UniDAC Crack

Devart UniDAC Crack 通用数据访问组件(UniDAC)是一个强大的非可视化跨数据库数据访问组件库&#xff0c;适用于Delphi、Delphi for.NET、CBuilder和Lazarus(Free Pascal)。我们将长期成功开发的经验结合到一个产品中&#xff0c;提供对流行数据库服务器的统一访问&#xff0c;…

Sublime Text 4 激活教程(Windows+Mac)

下载安装 官网 https://www.sublimetext.com 点击跳转 2023.7.21 版本为4143 Windows激活方式 一、激活License方式 入口在菜单栏中"Help” -> “Enter License” 注意格式&#xff0c;可能会过期失效&#xff0c;失效就用方式二 Mifeng User Single User License E…

SUSE宣布推出免费RHEL分叉以保留企业级Linux的选择权

导读在Red Hat宣布将限制AlmaLinuxOS或Rocky Linux等社区发行版对其公共仓库的访问后&#xff0c;最近Red Hat与IBM之间发生了一些争论&#xff0c;有鉴于此&#xff0c;SUSE今天宣布计划为RHEL和CentOS用户提供一个免费的替代方案。 SUSE已经开发了SUSE Linux Enterprise (SLE…

【数据挖掘】PCA/LDA/ICA:A成分分析算法比较

一、说明 在深入研究和比较算法之前&#xff0c;让我们独立回顾一下它们。请注意&#xff0c;本文的目的不是深入解释每种算法&#xff0c;而是比较它们的目标和结果。 如果您想了解更多关于PCA和ZCA之间的区别&#xff0c;请查看我之前基于numpy的帖子&#xff1a; PCA 美白与…

Fatdog64 Linux 814发布

导读Fatdog64 Linux是一个小型、桌面、64位的Linux发行版。 最初是作为Puppy Linux的衍生品&#xff0c;并增加了一些应用程序。该项目最新的版本&#xff0c;Fatdog64 814&#xff0c;是8xx系列的最后一个版本&#xff0c;未来的版本将转向9xx基础。 尽管它是该系列的最后一个…

红黑树概念

这里写目录标题 红黑树概念红黑树的性质红黑树节点的定义红黑树的插入 红黑树概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&…