- 文档
【免费下载链接】rfcs
RFCs for changes to Rust
本篇以 Rust 官方 RFC 仓库中的 1845-shared-from-slice.md 为骨架,系统讲解"从共享切片构造引用计数堆切片"这一标准库 API 设计:通过为Rc/Arc实现From<&[T]>、From<&str>、From<Vec<T>>、From<Box<T>>等转换,让字符串驻留(string interning)、切片缓存与无拷贝共享成为可能。读完本文,你将理解这套 API 的动机、底层RcBox内存布局与unsafe实现原理、Copy/Clone两种路径的取舍,以及Arc版本与备选方案的来龙去脉。
提案概览:RFC 1845 究竟要加什么
RFC 1845(Feature Name 为shared_from_slice,对应 Rust 实现跟踪问题 rust-lang/rust#40475)提议为标准库的引用计数智能指针新增一组Fromtrait 实现,目标类型统一是非定长(unsized)元素类型[T]与str:
| 源类型 | 目标类型 | 对T的约束 |
|---|---|---|
&[T](共享切片) | Rc<[T]> | T: Clone或T: Copy |
&str(字符串切片) | Rc<str> | 无(UTF-8 字节直接拷贝) |
Vec<T>(拥有型向量) | Rc<[T]> | 无(元素按位搬移,无需Clone/Copy) |
Box<T>(T: ?Sized) | Rc<T> | 无(支持非定长目标,如Box<[T]>、Box<str>) |
| 上述全部 | 对应的Arc版本 | 与Rc相同 |
其中前两类是该提案的核心,后两类(Vec、Box)是对 API 面的完整性补充。值得说明的是,Rc<[T]>、Rc<str>这类"引用计数的非定长值"在当时的标准库中尚无法从切片直接构造——Rc::new只接受定长类型,而当时Rc::new(vec![...].into_boxed_slice())一类路径并不存在,用户只能手工unsafe。这正是 RFC 1845 要解决的问题。
动机一:缓存与可控字符串驻留(string interning)
为什么Rc<str>适合做驻留
所谓字符串驻留,是指让内容相同的字符串在内存中只保留一份,通过指针相等(pointer equality)来判定内容相等,从而节省内存并加速比较。Rc<str>是理想的驻留载体:
- 从
&str构造Rc<str>时只需一次分配 + 一次字节拷贝; Rc<str>的clone()只增加强引用计数,不复制内容,代价极低;- 可以放进
HashSet<Rc<str>>之类的容器,且Rc提供了ptr_eq用于指针级比较。
RFC 给出了一个完整的最小示例,展示"缓存字符串并复用同一份Rc"的完整流程:
#![feature(ptr_eq)] #![feature(shared_from_slice)] use std::rc::Rc; use std::collections::HashSet; use std::mem::drop; fn cache_str(cache: &mut HashSet<Rc<str>>, input: &str) -> Rc<str> { // If the input hasn't been cached, do it: if !cache.contains(input) { cache.insert(input.into()); } // Retrieve the cached element. cache.get(input).unwrap().clone() } let first = "hello world!"; let second = "goodbye!"; let mut set = HashSet::new(); // Cache the slices: let rc_first = cache_str(&mut set, first); let rc_second = cache_str(&mut set, second); let rc_third = cache_str(&mut set, second); // The contents match: assert_eq!(rc_first.as_ref(), first); assert_eq!(rc_second.as_ref(), second); assert_eq!(rc_third.as_ref(), rc_second.as_ref()); // It was cached: assert_eq!(set.len(), 2); drop(set); assert_eq!(Rc::strong_count(&rc_first), 1); assert_eq!(Rc::strong_count(&rc_second), 2); assert_eq!(Rc::strong_count(&rc_third), 2); assert!(Rc::ptr_eq(&rc_second, &rc_third));注意其中几个细节:
cache.contains(input)借助Rc<str>对&str的Borrow实现完成内容查找;input.into()正是提案中的From<&str> for Rc<str>,在 nightly 下需要开启shared_from_slice特性门;- 两次缓存
second后,set中只有 2 个元素,且rc_second与rc_third通过Rc::ptr_eq证明指向同一份堆内存; drop(set)后强引用计数归位(rc_first为 1、rc_second/rc_third为 2),印证驻留行为。
典型应用场景
RFC 明确指出,这类缓存在现实中最典型的场景是AST(抽象语法树):树中同一个字符串字面量(如 XML 文档中的命名空间名)会被反复出现,逐节点各自持有String会造成大量重复分配。用HashSet<Rc<str>>驻留后,每个唯一字符串只在堆上存在一份,AST 节点只保存引用计数句柄。当时的tendrilcrate 也采用了类似思路——其文档中的描述是"Buffer sharing is accomplished through thread-local (non-atomic) reference counting"(通过线程本地、非原子的引用计数实现缓冲区共享),与 RFC 的目标一致。
为什么还需要From<&[T]>而不仅是From<&str>
只做&str不够,因为并非所有数据都是合法 UTF-8:
- 非 UTF-8 字节串(即
&[u8])同样值得驻留; Path、OsStr等类型内部也是非 UTF-8 字节序列,From<&[T]>可被它们复用。
因此 RFC 同时给出From<&[T]> for Rc<[T]>,让字节切片、Path/OsStr等场景也能低成本进入引用计数世界。
动机二:为unsafe代码提供安全抽象
在当前(指 RFC 撰写时的)Rust 状态下,从切片构造Rc<[T]>必须编写大量unsafe代码:需要手工分配一块能容纳RcBox<[T]>(引用计数头部 + 数据)的内存、初始化强弱引用计数、拷贝数据,再构造胖指针。这类代码散布在第三方库中,一旦内存布局判断出错就是未定义行为。RFC 认为,既然实现的安全性至关重要,就应当由标准库来完成并接受审查,让普通用户通过From得到完全安全的体验。
动机三:RcBox不公开,第三方无法安全实现
进一步地,即使第三方愿意写unsafe,也缺乏必要的基础设施:
std::rc内部的RcBox(引用计数结构体)并未公开,外部实现必须臆测Rc的内部布局,极不可靠;- 备选方案是从零自研一套
Rc,但这需要大量特性门(feature gate),在当时的 stable Rust 上不可行。
这两个约束共同说明:该 API 只能由标准库提供,第三方 crate 替代不了。仓库中的 README.md 也印证了这一点——"对std的大规模新增"正是必须走 RFC 流程的改动类型(见 "Large additions tostd")。
动机四:Arc版本的取舍
RFC 同时论证了为Arc增加同构 API 的必要性:
- 同步化的
.clone()开销通常大于Arc<Box<str>>中转的成本,但一旦克隆完成,Arc<str>因数据局部性更好,解引用通常更便宜; Rc的大部分动机同样适用于Arc,只是使用场景更少(如多线程驻留可以用Arc<Mutex<HashSet<Arc<str>>>>);- 由于
Rc与Arc内部布局高度相似(RcBoxvsArcInner),实现几乎可以逐字复用。
因此 RFC 的结论是:为Arc增加From<&[T]> for Arc<[T]>与From<&str> for Arc<str>是合理且一致的。
详细设计:从既有rustc_private实现出发
标准库里已有一个"半成品"
RFC 指出,alloc::rc中其实已经存在一个近似实现Rc<str>::__from_str,但它被藏在rustc_private特性门下,该门在可预见的未来不会稳定。当时的实现如下:
impl Rc<str> { /// Constructs a new `Rc<str>` from a string slice. #[doc(hidden)] #[unstable(feature = "rustc_private", reason = "for internal use in rustc", issue = "0")] pub fn __from_str(value: &str) -> Rc<str> { unsafe { // Allocate enough space for `RcBox<str>`. let aligned_len = 2 + (value.len() + size_of::<usize>() - 1) / size_of::<usize>(); let vec = RawVec::<usize>::with_capacity(aligned_len); let ptr = vec.ptr(); forget(vec); // Initialize fields of `RcBox<str>`. *ptr.offset(0) = 1; // strong: Cell::new(1) *ptr.offset(1) = 1; // weak: Cell::new(1) ptr::copy_nonoverlapping(value.as_ptr(), ptr.offset(2) as *mut u8, value.len()); // Combine the allocation address and the string length into a fat pointer to `RcBox`. let rcbox_ptr: *mut RcBox<str> = mem::transmute([ptr as usize, value.len()]); assert!(aligned_len * size_of::<usize>() == size_of_val(&*rcbox_ptr)); Rc { ptr: Shared::new(rcbox_ptr) } } } }这段代码已经揭示出核心的内存布局思想:RcBox<str>= 两个usize的计数头部(strong、weak)+ 数据区,str的长度以"宽指针(fat pointer)"的形式存在,而非直接存于分配块中。分配时按usize对齐向上取整(aligned_len),先布置计数,再用ptr::copy_nonoverlapping拷入字节,最后把"分配地址 + 长度"组合成指向RcBox<str>的胖指针,并以assert!校验分配尺寸与实际对象尺寸一致。
RFC 的思路正是:复用这个实现的主体,推广到Vec和切片,针对&str做特化,并补齐文档。
Copy与Clone的分歧:一条快路径、一条通用路径
对From<&[T]> for Rc<[T]>而言,T的约束决定实现策略:
- 若
T: Copy,可以用ptr::copy_nonoverlapping按位整体拷贝——这对Copy类型天然内存安全,速度最快; - 若
T仅Clone,则必须在一个循环里逐个T::clone()写入,速度较慢,但覆盖类型面更广。
RFC 建议两者都实现:用default fn from提供T: Clone的通用版本,再以T: Copy的具体实现覆盖它(代码中可见default fn关键字,这是当时"特化(specialization)"语境下的写法,相关设计可参见仓库中的 1210-impl-specialization.md)。
Vec<T>与Box<T>:无需Clone/Copy
From<Vec<T>> for Rc<[T]>:输入向量已经拥有合法的T元素,实现只需把这些元素按位搬进新的RcBox分配块,再将原Vec长度置零(vec.set_len(0))防止双重释放。此时T无需Copy也无需Clone。RFC 坦诚指出:当时无法复用Vec原有的内存(受限于Rc的内部布局),只能新分配、旧块释放,未来或可改进;From<Box<T>> for Rc<T>:语义与Vec版本一致,但T: ?Sized,即支持把Box<[T]>、Box<str>甚至 trait object 直接转为Rc。实现上需要额外考虑T的对齐(align = max(align_of_val(&*boxed), align_of::<usize>()))与数据区起始的偏移(offset_of_unsafe!(RcBox<T>, value))。
建议实现的完整骨架
RFC 给出了完整的参考实现。核心是一个可复用的 unsafe 辅助函数slice_to_rc:
#[inline(always)] unsafe fn slice_to_rc<'a, T, U, W, C>(src: &'a [T], cast: C, write_elems: W) -> Rc<U> where U: ?Sized, W: FnOnce(&mut [T], &[T]), C: FnOnce(*mut RcBox<[T]>) -> *mut RcBox<U> { // Compute space to allocate for `RcBox<U>`. let susize = mem::size_of::<usize>(); let aligned_len = 2 + (mem::size_of_val(src) + susize - 1) / susize; // Allocate enough space for `RcBox<U>`. let vec = RawVec::<usize>::with_capacity(aligned_len); let ptr = vec.ptr(); forget(vec); // Combine the allocation address and the slice length into a // fat pointer to RcBox<[T]>. let rbp = slice::from_raw_parts_mut(ptr as *mut T, src.len()) as *mut [T] as *mut RcBox<[T]>; // Initialize fields of RcBox<[T]>. (*rbp).strong.set(1); (*rbp).weak.set(1); write_elems(&mut (*rbp).value, src); // Recast to RcBox<U> and yield the Rc: let rcbox_ptr = cast(rbp); assert_eq!(aligned_len * susize, mem::size_of_val(&*rcbox_ptr)); Rc { ptr: Shared::new(rcbox_ptr) } }该函数通过两个泛型闭包参数实现"策略注入":write_elems决定如何填充数据(按位拷贝或逐个clone),cast决定如何把*mut RcBox<[T]>重铸为*mut RcBox<U>(如&str路径中把[u8]的 box 指针转为RcBox<str>指针)。在此基础上,四个From实现如下:
#[unstable(feature = "shared_from_slice", reason = "TODO", issue = "TODO")] impl<T> From<Vec<T>> for Rc<[T]> { /// Constructs a new `Rc<[T]>` from a `Vec<T>`. /// The allocated space of the `Vec<T>` is not reused, /// but new space is allocated and the old is deallocated. /// This happens due to the internal layout of `Rc`. /// /// # Examples /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let arr = [1, 2, 3]; /// let vec = vec![Box::new(1), Box::new(2), Box::new(3)]; /// let rc: Rc<[Box<usize>]> = Rc::from(vec); /// assert_eq!(rc.len(), arr.len()); /// for (x, y) in rc.iter().zip(&arr) { /// assert_eq!(**x, *y); /// } /// ``` #[inline] fn from(mut vec: Vec<T>) -> Self { unsafe { let rc = slice_to_rc(vec.as_slice(), |p| p, |dst, src| ptr::copy_nonoverlapping( src.as_ptr(), dst.as_mut_ptr(), src.len()) ); // Prevent vec from trying to drop the elements: vec.set_len(0); rc } } } #[unstable(feature = "shared_from_slice", reason = "TODO", issue = "TODO")] impl<'a, T: Clone> From<&'a [T]> for Rc<[T]> { /// Constructs a new `Rc<[T]>` by cloning all elements from the shared slice /// [`&[T]`][slice]. The length of the reference counted slice will be exactly /// the given [slice]. /// /// # Examples /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// #[derive(PartialEq, Clone, Debug)] /// struct Wrap(u8); /// /// let arr = [Wrap(1), Wrap(2), Wrap(3)]; /// let rc: Rc<[Wrap]> = Rc::from(arr.as_ref()); /// assert_eq!(rc.as_ref(), &arr); // The elements match. /// assert_eq!(rc.len(), arr.len()); // The lengths match. /// ``` /// /// Using the [`Into`][Into] trait: /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// #[derive(PartialEq, Clone, Debug)] /// struct Wrap(u8); /// /// let rc: Rc<[Wrap]> = arr.as_ref().into(); /// assert_eq!(rc.as_ref(), &arr); // The elements match. /// assert_eq!(rc.len(), arr.len()); // The lengths match. /// ``` /// /// [Into]: https://doc.rust-lang.org/std/convert/trait.Into.html /// [slice]: https://doc.rust-lang.org/std/primitive.slice.html #[inline] default fn from(slice: &'a [T]) -> Self { unsafe { slice_to_rc(slice, |p| p, |dst, src| { for (d, s) in dst.iter_mut().zip(src) { ptr::write(d, s.clone()) } }) } } } #[unstable(feature = "shared_from_slice", reason = "TODO", issue = "TODO")] impl<'a, T: Copy> From<&'a [T]> for Rc<[T]> { /// Constructs a new `Rc<[T]>` from a shared slice [`&[T]`][slice]. /// All elements in the slice are copied and the length is exactly that of /// the given [slice]. In this case, `T` must be `Copy`. /// /// # Examples /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let arr = [1, 2, 3]; /// let rc = Rc::from(arr); /// assert_eq!(rc.as_ref(), &arr); // The elements match. /// assert_eq!(rc.len(), arr.len()); // The length is the same. /// ``` /// /// Using the [`Into`][Into] trait: /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let arr = [1, 2, 3]; /// let rc: Rc<[u8]> = arr.as_ref().into(); /// assert_eq!(rc.as_ref(), &arr); // The elements match. /// assert_eq!(rc.len(), arr.len()); // The length is the same. /// ``` /// /// [Into]: ../../std/convert/trait.Into.html /// [slice]: ../../std/primitive.slice.html #[inline] fn from(slice: &'a [T]) -> Self { unsafe { slice_to_rc(slice, |p| p, <[T]>::copy_from_slice) } } } #[unstable(feature = "shared_from_slice", reason = "TODO", issue = "TODO")] impl<'a> From<&'a str> for Rc<str> { /// Constructs a new `Rc<str>` from a [string slice]. /// The underlying bytes are copied from it. /// /// # Examples /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let slice = "hello world!"; /// let rc: Rc<str> = Rc::from(slice); /// assert_eq!(rc.as_ref(), slice); // The elements match. /// assert_eq!(rc.len(), slice.len()); // The length is the same. /// ``` /// /// Using the [`Into`][Into] trait: /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let slice = "hello world!"; /// let rc: Rc<str> = slice.into(); /// assert_eq!(rc.as_ref(), slice); // The elements match. /// assert_eq!(rc.len(), slice.len()); // The length is the same. /// ``` /// /// This can be useful in doing [string interning], and caching your strings. /// /// ``` /// // For Rc::ptr_eq /// #![feature(ptr_eq)] /// /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// use std::collections::HashSet; /// use std::mem::drop; /// /// fn cache_str(cache: &mut HashSet<Rc<str>>, input: &str) -> Rc<str> { /// // If the input hasn't been cached, do it: /// if !cache.contains(input) { /// cache.insert(input.into()); /// } /// /// // Retrieve the cached element. /// cache.get(input).unwrap().clone() /// } /// /// let first = "hello world!"; /// let second = "goodbye!"; /// let mut set = HashSet::new(); /// /// // Cache the slices: /// let rc_first = cache_str(&mut set, first); /// let rc_second = cache_str(&mut set, second); /// let rc_third = cache_str(&mut set, second); /// /// // The contents match: /// assert_eq!(rc_first.as_ref(), first); /// assert_eq!(rc_second.as_ref(), second); /// assert_eq!(rc_third.as_ref(), rc_second.as_ref()); /// /// // It was cached: /// assert_eq!(set.len(), 2); /// drop(set); /// assert_eq!(Rc::strong_count(&rc_first), 1); /// assert_eq!(Rc::strong_count(&rc_second), 2); /// assert_eq!(Rc::strong_count(&rc_third), 2); /// assert!(Rc::ptr_eq(&rc_second, &rc_third)); /// /// [string interning]: https://en.wikipedia.org/wiki/String_interning fn from(slice: &'a str) -> Self { // This is safe since the input was valid utf8 to begin with, and thus // the invariants hold. unsafe { let bytes = slice.as_bytes(); slice_to_rc(bytes, |p| p as *mut RcBox<str>, <[u8]>::copy_from_slice) } } } #[unstable(feature = "shared_from_slice", reason = "TODO", issue = "TODO")] impl<T: ?Sized> From<Box<T>> for Rc<T> { /// Constructs a new `Rc<T>` from a `Box<T>` where `T` can be unsized. /// The allocated space of the `Box<T>` is not reused, /// but new space is allocated and the old is deallocated. /// This happens due to the internal layout of `Rc`. /// /// # Examples /// /// ``` /// #![feature(shared_from_slice)] /// use std::rc::Rc; /// /// let arr = [1, 2, 3]; /// let vec = vec![Box::new(1), Box::new(2), Box::new(3)].into_boxed_slice(); /// let rc: Rc<[Box<usize>]> = Rc::from(vec); /// assert_eq!(rc.len(), arr.len()); /// for (x, y) in rc.iter().zip(&arr) { /// assert_eq!(**x, *y); /// } /// ``` #[inline] fn from(boxed: Box<T>) -> Self { unsafe { // Compute space to allocate + alignment for `RcBox<T>`. let sizeb = mem::size_of_val(&*boxed); let alignb = mem::align_of_val(&*boxed); let align = cmp::max(alignb, mem::align_of::<usize>()); let size = offset_of_unsafe!(RcBox<T>, value) + sizeb; // Allocate the space. let alloc = heap::allocate(size, align); // Cast to fat pointer: *mut RcBox<T>. let bptr = Box::into_raw(boxed); let rcbox_ptr = { let mut tmp = bptr; ptr::write(&mut tmp as *mut _ as *mut * mut u8, alloc); tmp as *mut RcBox<T> }; // Initialize fields of RcBox<T>. (*rcbox_ptr).strong.set(1); (*rcbox_ptr).weak.set(1); ptr::copy_nonoverlapping( bptr as *const u8, (&mut (*rcbox_ptr).value) as *mut T as *mut u8, sizeb); // Deallocate box, we've already forgotten it. heap::deallocate(bptr as *mut u8, sizeb, alignb); // Yield the Rc: assert_eq!(size, mem::size_of_val(&*rcbox_ptr)); Rc { ptr: Shared::new(rcbox_ptr) } } } }几个值得点出的实现要点:
- 零长度切片/向量同样适用:RFC 明确说明这些实现同样适用于零大小的切片与向量;
Vec版本先搬数据再set_len(0):避免Vec析构时对已搬走的元素二次释放(双重 free);str版本复用[u8]路径:&str先转为as_bytes(),用<[u8]>::copy_from_slice做按位拷贝,再把RcBox<[u8]>指针重铸为RcBox<str>——因为输入本来就是合法 UTF-8,不变式成立,因此安全;Box版本处理非定长与对齐:用size_of_val/align_of_val动态求得T的尺寸与对齐,分配后把Box原内存解分配(heap::deallocate),再将新分配块与T的元数据组合成RcBox<T>胖指针;- 每个实现末尾都有
assert_eq!校验"分配的字节数 == 对象实际尺寸",相当于对布局推演的一层运行时保险。
RFC 同时指出:未来随着更多安全抽象(如更完善的分配器 API)落地,这些实现可以用更少的unsafe重写,但API 本身不变,因此永远不会构成破坏性变更。
Arc版本:机械替换即可
由于Rc与Arc布局同构,RFC 给出了简洁的映射规则,只需在Rc实现上做四处替换:
slice_to_rc→slice_to_arcRcBox→ArcInnerrcbox_ptr→arcinner_ptrRc→Arc
即From<&[T]> for Arc<[T]>、From<&str> for Arc<str>(以及Vec、Box对应版本)均可由此得到。这也体现了标准库中Rc/Arc长期以来"共享一套实现逻辑"的惯例。
教学方式
RFC 认为,这些 API 的文档写在impl上即可自足:每个From实现都配有# Examples,演示Rc::from(...)与.into()两种等价写法,并附上驻留场景的完整示例。用户不需要额外学习新概念,只需要知道"&str/&[T]/Vec<T>/Box<T>可以into()成引用计数版本"。
缺点
唯一的显著缺点非常直白:增加标准库的体积(需要维护一组新的From实现及其文档)。这是所有标准库新增 API 的共同代价。
备选方案:RFC 讨论了哪些岔路
RFC 列出并逐条评估了多个备选设计,是理解最终取舍的关键:
- 只做
T: Copy,跳过T: Clone——少一半实现,但会漏掉大量非Copy但Clone的类型; - 交给第三方库实现——被动机部分否定:
RcBox不公开、自研Rc需要大量特性门,收益却很小; - 只做
Rc,不做Arc——被动机部分的Arc论证否定; - 跳过
Vec<T>或只做Vec<T>——两者都会让 API 面不完整; - 跳过
Box<T>——同理; - 用
AsRef替代From(即impl From<AsRef<str>> for Rc<str>)——虽更省事,但可能撞上孤儿规则(coherence)问题,阻碍其他想要的实现,RFC 倾向不用; - 把转换做成
&str/&[T]上的方法(如.into_rc_str()、.into_rc_slice())——RFC 倾向使用From实现,理由是一致性与省心("只需记住一个方法名")。它也把String::into_boxed_str、Vec::into_boxed_slice列为参照:区别在于本提案走Fromtrait,且目标是共享智能指针而非独占的Box; - 同时在
Rc/Arc上提供关联函数,例如:
impl<T: Clone> Rc<[T]> { fn from_slice(slice: &[T]) -> Self; } impl Rc<str> { fn from_str(slice: &str) -> Self; } impl<T: Clone> Arc<[T]> { fn from_slice(slice: &[T]) -> Self; } impl Arc<str> { fn from_str(slice: &str) -> Self; }这一条作为"同时提供"的附加项被提出,与From实现并不互斥。
未解决的问题
RFC 在合并时遗留了一个开放问题:是否要为Rc<[T]>增加一个特化版的make_mut,形如:
impl<T> Rc<[T]> where T: Clone { fn make_mut_slice(this: &mut Rc<[T]>) -> &mut [T] }即让"写时复制"(copy-on-write)也能作用于引用计数的切片。该问题留给后续设计决定,不影响本提案的主体 API。
在 Rust RFC 体系中的位置
要理解 RFC 1845 为什么是"水到渠成"的一步,可以把它放到本仓库中一系列关联 RFC 的上下文里:
- 0529-conversion-traits.md:确立了
From/Into通用转换 trait,让Rc::from(vec)、slice.into()这类写法成为语言惯例——本提案正是From体系的直接受益者; - 0490-dst-syntax.md:确立了
T: ?Sized语法,非定长类型参数(如Rc<[T]>、Rc<str>中的[T]/str)由此可表达; - 0982-dst-coercion.md:通过
Unsize/CoerceUnsized让Rc<T>全面参与 DST 强转系统(如Rc<[i32; 42]>→Rc<[i32]>),是"引用计数非定长值"得以落地的基础设施; - 0401-coercions.md:系统描述了 Rust 的类型转换体系(子类型、强转、cast),其中对智能指针参与 DST 的设想在 0982-dst-coercion.md 中被继承并具体化;
- 仓库侧的流程约束可见 libs_changes.md 与 README.md:对标准库的大规模新增必须走 RFC,而本提案正是"库改动需先成文"的典型样本;RFC 文件本身遵循 0000-template.md 规定的 Summary / Motivation / Detailed design / Drawbacks / Alternatives / Unresolved questions 骨架,并经 generate-book.py 与 book.toml 编排进 The Rust RFC Book 的章节体系。
结语:一条 API 的两重价值
RFC 1845 表面上是"给Rc/Arc加几个From实现",实质上有两重价值:对用户,它把HashSet<Rc<str>>式的可控字符串驻留、AST/XML 命名空间的去重缓存从"手写 unsafe"变成一行.into();对标准库,它填补了"从切片构造引用计数非定长值"的空白,为后续Rc<[T]>/Arc<str>家族的进一步演化(如写时复制)铺平了道路。理解其背后的RcBox布局、Copy/Clone双路径与Vec/Box的所有权搬运逻辑,也就理解了 Rust 智能指针 API 设计的典型范式。
如需继续深入,可直接研读本仓库中的原始提案 1845-shared-from-slice.md(含全部代码与参考链接),以及关联的 0529-conversion-traits.md、0982-dst-coercion.md、0490-dst-syntax.md 三份 RFC。
- 文档
【免费下载链接】rfcs
RFCs for changes to Rust
相关推荐
Rust RFC 1152 解读:为 `str` 与 `String` 补齐切片对称转换 API(`into_string` 与 `into_boxed_str`)
Rust RFC 1152 解读:为 str 与 String 补齐切片对称转换 API( into_string 与 into_boxed_str ) 导读
文档Rust 切片(Slice)实战指南:从 `&[T]` 到 `&str` 的完整掌握
Rust 切片(Slice)实战指南:从 & T 到 &str 的完整掌握 本文基于 rust by practice 仓库的 切片练习章节 https://l
文档教程示例工程Rust 智能指针之 `Rc<T>`:引用计数与多所有权共享实战(TRPL 第 15 章)
Rust 智能指针之 Rc<T :引用计数与多所有权共享实战(TRPL 第 15 章) 导读 Rc<T (Reference Counting,引用计数)是 R
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考