如何构建一个Rc<str>或者Rc<[T]>?

32
我想创建一个 Rc<str>,因为我想减少访问 Rc<String> 时需要跟随 2 个指针的间接性。我需要使用 Rc,因为我确实拥有共享所有权。在 另一个问题 中,我详细阐述了我在字符串类型上遇到的更具体的问题。 Rc 有一个 ?Sized 约束
pub struct Rc<T: ?Sized> { /* fields omitted */ }

我也听说Rust 1.2将配备适当的支持,以在中存储不定大小的类型,但我不确定这与1.1有什么区别。

以上面为例,我的naive attempt(还有这个是构建一个String)会失败:

use std::rc::Rc;

fn main() {
    let a: &str = "test";
    let b: Rc<str> = Rc::new(*a);
    println!("{}", b);
}

error[E0277]: the trait bound `str: std::marker::Sized` is not satisfied
 --> src/main.rs:5:22
  |
5 |     let b: Rc<str> = Rc::new(*a);
  |                      ^^^^^^^ `str` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: required by `<std::rc::Rc<T>>::new`

很明显,为了创建一个Rc<str>,我需要复制整个字符串:RcBox本身将是一个不定长类型,存储字符串本身以及弱引用和强引用指针——上面的天真代码甚至没有意义。
我被告知不能实例化这种类型,而是要实例化具有大小的TRc<T>,然后将其强制转换为不定长类型。给出的示例是存储特质对象:首先创建Rc<ConcreteType>,然后强制转换为Rc<Trait>。但这也没有意义:既不这样,也不那样工作(无论如何,你都不能从&strString强制转换为str)。

因为访问 Rc<String> 需要遵循 2 个指针(此外,我需要 Rc,因为我真正拥有共享所有权)。我在另一个问题中详细说明了我围绕字符串类型遇到的更具体的问题。我的暂定定义 Rc<Utf16Str> 还需要一个不定大小的类型 Utf16Str(它将具有与 Rc<[u16]> 相同的布局)。 - darque
请参阅[是否可以从Vec<T>创建Arc<T]>? - Shepmaster
2个回答

43

截至 Rust 1.21.0,根据 RFC 1845 的规定,现在可以创建 Rc<str>Arc<str>

use std::rc::Rc;
use std::sync::Arc;

fn main() {
    let a: &str = "hello world";
    let b: Rc<str> = Rc::from(a);
    println!("{}", b);

    // or equivalently:
    let b: Rc<str> = a.into();
    println!("{}", b);

    // we can also do this for Arc,
    let a: &str = "hello world";
    let b: Arc<str> = Arc::from(a);
    println!("{}", b);
}

(游乐场)

请查看<Rc as From<&str>><Arc as From<&str>>


10

通过强制转换和从固定大小数组进行 as 转换,可以创建一个 Rc<[T]>,例如可以按照以下方式进行强制转换:

use std::rc::Rc;

fn main() {
    let x: Rc<[i32; 4]> = Rc::new([1, 2, 3, 4]);

    let y: Rc<[i32]> = x;

    println!("{:?}", y);
}

然而,对字符串来说这种方法不起作用,因为它们没有可以创建第一个值的原始固定大小的等价物。可以通过不安全的方式实现,例如创建UTF-8编码的Rc<[u8]>并将其转换为Rc<str>。理论上可能会有一个crates.io上的板条箱,但我目前找不到一个。

另一种选择是owning_ref,它不是std::rc::Rc本身,但应该允许例如获取指向Rc<String>中的RcRef<...,str>。 (如果统一使用RcRef代替Rc,除了构造之外,这种方法将效果最佳。)

extern crate owning_ref;
use owning_ref::RcRef;
use std::rc::Rc;

fn main() {
    let some_string = "foo".to_owned();

    let val: RcRef<String> = RcRef::new(Rc::new(some_string));

    let borrowed: RcRef<String, str> = val.map(|s| &**s);

    let erased: RcRef<owning_ref::Erased, str> = borrowed.erase_owner();
}

擦除意味着>可以来自多个不同的来源,例如,RcRef也可以来自字符串字面量。
注意:在撰写本文时,使用RcRef进行擦除需要夜间编译器,并且取决于具有nightly功能的owning_ref。
[dependencies]
owning_ref = { version = "0.1", features = ["nightly"] }

1
那么当[T]长度在编译时未知时,就无法创建Rc <[T]>,对吗?(但为什么呢? Rc不是在堆上分配的吗?因为它是一个fat指针,所以它已经“知道”自己的大小) - darque
是的,Rc<[T]> 无法通过动态长度强制转换来构建。然而这个概念完全有道理,唯一的问题是没有好的方法来构建它。 - huon
此外,访问RcRef内部的String是否需要遵循这两个指针?(整个问题在于Rc<String>有一个指向String的指针,该指针指向字符串的开头...)。我无法确定this是否只有指向String的指针。 - darque
@darque 它已经可以了 对于某些未定大小的类型,比如特质。这是 Rust 1.1 新增的功能,如果我没记错的话。 - Shepmaster
@darque,Rc<str>已经可以使用:如果您可以创建一个(例如从UTF-8 Rc<[u8]>Rc<str>transmute),那么一切都将正常工作,只是没有直接构造它的安全方法。RcRef实现仅需要单个指针访问来读取str数据。 - huon
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接