news 2026/10/15 7:42:16

Rust RFC 1845 解读:shared_from_slice——用 `From` 从切片共享构造 `Rc<[T]>`、`Rc<str>` 与 `Arc` 版本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rust RFC 1845 解读:shared_from_slice——用 `From` 从切片共享构造 `Rc<[T]>`、`Rc<str>` 与 `Arc` 版本
  • 文档

【免费下载链接】rfcs

RFCs for changes to Rust

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载

本篇以 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_arc
  • RcBox→ArcInner
  • rcbox_ptr→arcinner_ptr
  • Rc→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 列出并逐条评估了多个备选设计,是理解最终取舍的关键:

  1. 只做T: Copy,跳过T: Clone——少一半实现,但会漏掉大量非Copy但Clone的类型;
  2. 交给第三方库实现——被动机部分否定:RcBox不公开、自研Rc需要大量特性门,收益却很小;
  3. 只做Rc,不做Arc——被动机部分的Arc论证否定;
  4. 跳过Vec<T>或只做Vec<T>——两者都会让 API 面不完整;
  5. 跳过Box<T>——同理;
  6. 用AsRef替代From(即impl From<AsRef<str>> for Rc<str>)——虽更省事,但可能撞上孤儿规则(coherence)问题,阻碍其他想要的实现,RFC 倾向不用;
  7. 把转换做成&str/&[T]上的方法(如.into_rc_str()、.into_rc_slice())——RFC 倾向使用From实现,理由是一致性与省心("只需记住一个方法名")。它也把String::into_boxed_str、Vec::into_boxed_slice列为参照:区别在于本提案走Fromtrait,且目标是共享智能指针而非独占的Box;
  8. 同时在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

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载
上一篇:AutoUnipus:如何用Python自动化技术提升U校园学习效率
下一篇:Git 重命名远程仓库(Remote)的完整指南:从 `git remote rename` 到跟踪分支更新

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 7:41:47

Spring Boot企业招聘平台毕业设计:架构拆解与核心实现指南

1. 为什么毕业设计我最终选了"招聘平台"这个题目作为一个过来人&#xff0c;我知道计算机专业做毕业设计最头疼的事情&#xff0c;就是题目看起来五花八门&#xff0c;真正动手时却不知道怎么落地。我当时把导师给的题目清单翻来覆去看了好几遍&#xff0c;最后敲定了…

作者头像 李华
网站建设 2026/10/15 7:41:47

AI编程助手下的代码审查盲区:测试、安全与架构

直接输出博文正文&#xff0c;不加任何元信息说明。1. 先聊一个扎心的现实&#xff1a;提效是真的&#xff0c;烂摊子也是真的我所在的团队全面铺开AI编程助手大概有大半年了。刚开始那阵子&#xff0c;效率提升确实肉眼可见——以前一个中等复杂度的接口要写半天&#xff0c;现…

作者头像 李华
网站建设 2026/10/15 7:41:44

大号牛角椅结实吗?八斤以上承重更有保障

在寻找耐用且适合户外高频使用的塑料牛角椅时&#xff0c;建议重点关注椅子的重量与材质构成。一般来说&#xff0c;单重达到8斤以上的大号牛角椅&#xff0c;因采用PP加LGF长玻纤复合材料&#xff0c;具备较强的抗冲击性和结构稳定性&#xff0c;相比普通轻型塑料椅更耐摔、不…

作者头像 李华
网站建设 2026/10/15 7:41:40

语音芯片4种播放模式详解:单曲不循环/单曲循环/全部循环/随机播放

报站器到站语音播完一遍又一遍停不下来&#xff0c;按键提示音想让它播完就闭嘴却一直循环&#xff0c;广告机想让十几条音频轮着播、客户听两条就换内容又做不到。这类问题多数不在音频文件本身&#xff0c;在播放模式。很多工程师拿到语音芯片先把精力花在音量和选曲上&#…

作者头像 李华
网站建设 2026/10/15 7:41:32

Switch中的default分支:被忽视的兜底逻辑如何决定代码健壮性

1. 从一个小需求说起&#xff1a;为什么default常常被忽视1.1 switch语句的本质先聊一个几乎所有写代码的人都碰过的结构。switch语句&#xff0c;说白了就是把一个表达式和多个候选值做比较&#xff0c;命中哪个就执行哪个分支。比一长串if else if看着清爽&#xff0c;逻辑上…

作者头像 李华
网站建设 2026/10/15 7:41:14

人类记忆机制解析与增强技术

1. 记忆机制的本质探秘人类大脑的记忆系统就像一座庞大的档案馆&#xff0c;而潜意识则是这座档案馆里那位沉默寡言却效率惊人的档案管理员。这位"管理员"每天要处理约1100万比特的信息输入&#xff0c;但最终能被我们主动回忆的只有约40比特——这个惊人的数字对比揭…

作者头像 李华