rust:智能指针

c++中就有智能指针的概念:共享指针,独占指针。顾名思义,共享指针,是多个指针可以持有同一个对象;独占指针,只能有一个指针持有对象。

c++中的共享指针存在循环引用导致对象无法释放,进而导致内存泄漏的问题,为了解决这个问题,引入了weak指针。同样的,rust中也有共享指针,也同样存在这样的问题,并且rust编译器在编译阶段无法检查出这种错误。rust的解决办法和c++中的解决办法类似,引入了weak指针的概念。

像这种纯语法类的错误,而不是业务上的bug,rust编译器也无法检查出来,个人认为rust相对于c++的进步是很有限的。内存安全,包管理是两个显而易见的优势,其他方面,并不明显;而c++的优势更加明显,在内存管理方面,如果都使用共享指针,那么内存方面的错误也会大大减少,另外c++的生态更加丰富,并且能直接调用系统调用。c++的优势太多了。

Box<T>类似于c++中的独占指针,Rc<T>类似于共享指针。Rc<T>创建的对象是不可修改的,因为Rc<T>会导致共享所有权,如果每个Rc<T>都能修改对象,那么就违背了rust的借用规则要求,即在同一个时刻,只能有一个可以修改,由此引出了RefCell<T>,这是rust独有的特点。

  • 优先考虑使用 Box<T>或普通引用

  • 当需要多个所有者时使用 Rc<T>

  • 当需要多个所有者且需要修改时使用 Rc<RefCell<T>>。专门引出RefCell来实现对象的可变性,为什么不把Rc<T>直接实现成可变的呢,这样岂不是更简洁?

  • 同一个作用域内只能有一个可变引用,或者多个不可变引用,可变引用和不可变引用不能同时存在。类似于读写锁。

1 Box<T>

Box<T>是可以修改的。

rust 复制代码
fn main() {
    // 必须声明为 mut 才能修改 Box 中的值
    let mut boxed_number = Box::new(5);
    
    println!("原始值: {}", *boxed_number);
    
    // 修改 Box 中的值
    *boxed_number = 10;
    println!("修改后: {}", *boxed_number);
    
    // 通过可变引用修改
    let reference = &mut *boxed_number;
    *reference = 20;
    println!("再次修改: {}", boxed_number); // 会自动解引用
    
    // 使用 deref_mut
    use std::ops::DerefMut;
    *boxed_number.deref_mut() = 30;
    println!("通过 deref_mut 修改: {}", boxed_number);
}

2 Rc<T>

Rc<T>不可以修改。

rust 复制代码
use std::rc::Rc;

fn main() {
    // 创建一个字符串数据,用 Rc 包装
    let data = Rc::new(String::from("Hello, Rust!"));
    
    // 创建第一个引用计数指针
    let pointer1 = Rc::clone(&data);
    println!("pointer1: {}", pointer1);
    println!("当前引用计数: {}", Rc::strong_count(&data));
    
    // 创建第二个引用计数指针
    let pointer2 = Rc::clone(&data);
    println!("pointer2: {}", *pointer2);
    println!("当前引用计数: {}", Rc::strong_count(&data));
    
    {
        // 在作用域内创建第三个引用
        let pointer3 = Rc::clone(&data);
        println!("pointer3: {}", pointer3);
        println!("作用域内引用计数: {}", Rc::strong_count(&data));
    } // pointer3 离开作用域,引用计数减1
    
    // 作用域外,引用计数恢复
    println!("作用域外引用计数: {}", Rc::strong_count(&data));
    
    // 所有指针指向相同的数据
    println!("pointer1 和 pointer2 指向相同数据: {}", 
             Rc::ptr_eq(&pointer1, &pointer2));
    
    // 当所有 Rc 指针离开作用域,数据会被自动清理
    // 此时引用计数变为 0
}

3 RefCell<T>

3.1局部可变性

如下代码,self是不可变的,但是可以通过self修改self内部的成员,实现了可变性穿透,也是很NB的存在。

rust 复制代码
use std::cell::RefCell;

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

pub struct LimitTracker<'a, T: 'a + 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("Warnning: you've used up over 90");
    } else if percentage_of_max >= 0.75 {
      self.messenger.send("Warnning: you've used up over 75");
    }
 }
}

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

  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) {
      //如果sent_messages不声明为RefCell,那么sent_message在这里不能修改
      //因为self是不可变引用
      self.sent_messages.borrow_mut().push(String::from(message));
    }
  }

  #[test]
  fn it_sends_an_over_75_percent_warning_messages() {
    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);
  }
}

3.2拥有多重所有权的可变数据

rust 复制代码
use std::rc::Rc;
use std::cell::RefCell;

// 定义一个简单的图节点
#[derive(Debug)]
struct Node {
    value: i32,
    // 使用 Rc<RefCell<Node>> 实现多重所有权
    neighbors: Vec<Rc<RefCell<Node>>>,
}

impl Node {
    fn new(value: i32) -> Rc<RefCell<Self>> {
        Rc::new(RefCell::new(Node {
            value,
            neighbors: Vec::new(),
        }))
    }
    
    fn add_neighbor(&mut self, neighbor: Rc<RefCell<Node>>) {
        self.neighbors.push(neighbor);
    }
    
    fn print_with_neighbors(&self) {
        println!("Node {} has neighbors: {:?}", 
            self.value, 
            self.neighbors.iter().map(|n| n.borrow().value).collect::<Vec<_>>()
        );
    }
}

fn main() {
    // 创建三个节点
    let node1 = Node::new(1);
    let node2 = Node::new(2);
    let node3 = Node::new(3);
    
    // node1 连接到 node2 和 node3
    node1.borrow_mut().add_neighbor(Rc::clone(&node2));
    node1.borrow_mut().add_neighbor(Rc::clone(&node3));
    
    // node2 也连接到 node3(共享所有权)
    node2.borrow_mut().add_neighbor(Rc::clone(&node3));
    
    // 验证引用计数
    println!("Reference count of node3: {}", Rc::strong_count(&node3)); // 应该是3
    
    // 打印所有节点的邻居
    node1.borrow().print_with_neighbors();
    node2.borrow().print_with_neighbors();
    node3.borrow().print_with_neighbors();
    
    // 修改 node3 的值(通过任意一个引用)
    node3.borrow_mut().value = 33;
    
    // 查看修改是否生效
    println!("\nAfter modifying node3:");
    println!("Node3 value from node1's neighbor: {}", 
             node1.borrow().neighbors[1].borrow().value);
    println!("Node3 value from node2's neighbor: {}", 
             node2.borrow().neighbors[0].borrow().value);
    
    // 创建一个循环引用(双向连接)
    node3.borrow_mut().add_neighbor(Rc::clone(&node1));
    println!("\nAfter creating cycle:");
    println!("Reference count of node1: {}", Rc::strong_count(&node1));
}

4循环引用

循环引用,导致无法释放:

(1)定义结构体struct User,为User实现Drop trait,当User被释放的时候,Drop trait会被调用

(2)创建两User的两个对象alice和bob,alice和bob相互引用

(3)当进程退出的时候,看不到drop函数被调用,说明alice和bob两个对象没有被释放

rust 复制代码
use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct User {
    name: String,
    friends: RefCell<Vec<Rc<User>>>,
}

// 实现 Drop trait 来创建析构函数
impl Drop for User {
    fn drop(&mut self) {
        println!("🚮 User '{}' 被释放了", self.name);
    }
}

fn create_cycle_reference() {
    println!("=== 1. 创建循环引用(内存泄漏) ===");
    
    let alice = Rc::new(User {
        name: "Alice".to_string(),
        friends: RefCell::new(Vec::new()),
    });
    
    let bob = Rc::new(User {
        name: "Bob".to_string(),
        friends: RefCell::new(Vec::new()),
    });
    
    // 相互引用,形成循环
    alice.friends.borrow_mut().push(Rc::clone(&bob));
    bob.friends.borrow_mut().push(Rc::clone(&alice));
    
    println!("引用计数 - Alice: {}, Bob: {}", 
             Rc::strong_count(&alice), 
             Rc::strong_count(&bob));
    
    // 函数结束时,这些对象应该被释放,但由于循环引用,它们不会被释放
    println!("函数结束,应该看到释放消息吗?");
}

fn main() {
    create_cycle_reference();
    
    println!("\n=== main函数继续执行 ===");
    println("这里不会看到Alice和Bob的释放消息,因为它们还在内存中!");
    
    // 手动触发垃圾回收(在Rust中,实际是离开作用域时自动清理)
    println!("\n=== 程序结束 ===");
    // 由于内存泄漏,程序结束时Alice和Bob仍然不会被释放
}

使用weak指针,可以看到drop函数被调用:

(1)strong_count是强引用计数,也就是几个Rc指针持有对象,weak_count是弱引用计数,也就是几个Weak指针持有对象。对象的释放不受weak_count约束,当strong_count减为0时,即使weak_count不是0,那么也会释放对象。

(2)Weak指针不能单独创建,必须依赖Rc指针而存在,通过Rc指针的downgrade来获取Weak指针;通过Weak指针的upgrade方法,可以判断对象是不是还存在。

rust 复制代码
use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct User {
    name: String,
    friends: RefCell<Vec<Weak<User>>>,
}

impl Drop for User {
    fn drop(&mut self) {
        println!("✅ User '{}' 被正确释放了", self.name);
    }
}

fn create_without_cycle() {
    println!("=== 2. 使用Weak避免循环引用 ===");
    
    let alice = Rc::new(User {
        name: "Alice".to_string(),
        friends: RefCell::new(Vec::new()),
    });
    
    let bob = Rc::new(User {
        name: "Bob".to_string(),
        friends: RefCell::new(Vec::new()),
    });
    
    // 使用弱引用,不会形成循环
    alice.friends.borrow_mut().push(Rc::downgrade(&bob));
    bob.friends.borrow_mut().push(Rc::downgrade(&alice));
    
    println!("强引用计数 - Alice: {}, Bob: {}", 
             Rc::strong_count(&alice), 
             Rc::strong_count(&bob));
    
    // 显示弱引用数量
    println!("弱引用计数 - Alice: {}, Bob: {}", 
             Rc::weak_count(&alice), 
             Rc::weak_count(&bob));
    
    println!("函数结束,现在应该看到释放消息...");
}

fn main() {
    create_without_cycle();
    
    println!("\n=== main函数继续执行 ===");
    println!("Alice和Bob已经被正确释放!");
    
    // 创建一个临时的用户来演示生命周期
    println!("\n=== 3. 演示局部作用域 ===");
    {
        let temp_user = Rc::new(User {
            name: "临时用户".to_string(),
            friends: RefCell::new(Vec::new()),
        });
        
        println!("临时用户创建,强引用计数: {}", Rc::strong_count(&temp_user));
        // temp_user 在这个作用域结束时会被释放
    }
    println!("作用域结束,临时用户应该已被释放");
    
    println!("\n=== 程序结束 ===");
}
相关推荐
唐装鼠3 小时前
Rust transmute(deepseek)
开发语言·rust
laocooon5238578863 小时前
Rust 编程语言教学目录
开发语言·后端·rust
小希smallxi3 小时前
Rust语言入门
开发语言·后端·rust
进击的前栈5 小时前
Flutter跨平台网络图片缓存库cached_network_image鸿蒙化适配指导手册
开发语言·网络·rust
前端小天才8 小时前
element-ui图标偶现乱码问题的原因和修复方法
开发语言·ui·rust
Yuer20259 小时前
WebRTC 实时语音交互如何支持“可中断”?为什么状态机(FSM)是绕不开的方案
算法·rust·webrtc·fsm
Source.Liu1 天前
【Rust】函数
rust
huatian51 天前
Rust 语法整理
开发语言·后端·rust
九月生1 天前
Rust CLI 项目构建指南
rust