Rust中不可变引用可实现多指向,为什么还需要Rc<T>?
为什么有不可变引用还需要
Rc<T>? 你给出的引用版List可以编译,是依赖临时值生命周期延长和所有值生命周期完全嵌套对齐的特殊场景,本质还是借用,不是真正的多所有权,和Rc<T>的适用场景完全不一样,核心区别有以下几点:
- 第一,生命周期绑定的限制
引用本质是借用,必须严格遵守借用检查器的生命周期规则,你构造的List所有节点的生命周期都必须和最外层变量绑定,既没法把这个List作为返回值传给上层作用域,也没法动态插入、移除节点,只要有一个节点的生命周期短于整个List,直接就会编译报错。
而Rc<T>是真正持有值的所有权,内部通过引用计数管理值的存活时间,只要还有Rc指针持有这个值,它就不会被销毁,完全不需要绑定生命周期,你可以自由传递、赋值,跨作用域使用也不会有问题。
举个最简单的反例,你试试用引用版List实现一个返回列表的函数:
fn build_list() -> List<'???> { // 这里根本没法填写生命周期参数 let inner = Cons(10, &Nil); Cons(5, &inner) }
这个函数根本不可能编译通过,因为inner是函数内部的临时值,离开作用域就会被销毁,返回的引用直接变成悬垂指针。但换成Rc<T>实现的话就完全没有问题:
enum List { Cons(i32, Rc<List>), Nil, } use std::rc::Rc; use crate::List::{Cons, Nil}; fn build_list() -> Rc<List> { let inner = Rc::new(Cons(10, Rc::new(Nil))); Rc::new(Cons(5, inner)) }
完全可以正常编译使用。
- 第二,可变共享的支持
你示例的引用版List只能使用不可变引用,要是需要修改共享节点的内容,引用根本做不到,就算你用RefCell配合,还是逃不过生命周期的绑定限制。而Rc<T>可以配合RefCell<T>实现共享可变的需求,只要多个所有者都持有Rc<RefCell<T>>,就可以在任意地方安全修改内部的值,不用考虑生命周期问题。 - 第三,复杂数据结构的实现
像图、树这类存在互相引用、循环引用的复杂数据结构,用引用根本没法实现,你没法给所有节点安排合理的生命周期,也没法处理节点的动态销毁。而Rc<T>配合Weak<T>(用来避免循环引用导致的内存泄漏)可以很轻松实现这类结构,节点的销毁完全由引用计数自动管理。
最后补充下你示例和书中描述的差异:
你示例里的let a = Cons(5, &Cons(10, &Nil));能编译,是因为Rust会把表达式里的临时值的生命周期延长到和外层的a对齐,刚好满足生命周期规则。如果把代码拆成分开的作用域,就会和书中说的一样编译失败:
fn main() { let a; { let nil = Nil; let inner = Cons(10, &nil); a = Cons(5, &inner); } // 这里访问a就会报错,因为inner和nil已经被销毁了 }
这种拆分后的场景才是实际开发里更常见的情况,引用的生命周期限制会非常影响代码的灵活性,这时候就是Rc<T>的适用场景了。
内容的提问来源于stack exchange,提问作者nalzok
相关产品推荐
相关产品推荐

