32

I'd like to create an Rc<str> because I want reduce the indirection from following the 2 pointers that accessing an Rc<String> requires. I need to use an Rc because I truly have shared ownership. I detail in another question more specific issues I have around my string type.

Rc has a ?Sized bound:

pub struct Rc<T: ?Sized> { /* fields omitted */ }

I've also heard that Rust 1.2 will come with proper support for storing unsized types in an Rc, but I'm unsure how this differs from 1.1.

Taking the str case as example, my naive attempt (also this for building from a String) fails with:

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`

It's clear that in order to create an Rc<str>, I need to copy the whole string: RcBox would be itself an unsized type, storing the string itself alongside the weak and strong pointers — the naive code above doesn't even make sense.

I've been told that one can not instantiate such type, but instead instantiate an Rc<T> with a sized T and then coerce it to an unsized type. The example given is for the storing a trait object: first create Rc<ConcreteType> and then coerce to Rc<Trait>. But this doesn't make sense either: neither this nor this work (and you can't coerce from &str or String to str anyway).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
darque
  • 1,566
  • 1
  • 14
  • 22
  • Because accessing an `Rc` requires following 2 pointers (also, I need `Rc` because I truly have shared ownership). I detail [in another question](https://stackoverflow.com/questions/31685345/is-there-a-rust-library-with-an-utf-16-string-type-intended-for-writing-a-java) more specific issues I've around my string type. My tentative definition `Rc` would also require an unsized type `Utf16Str` (it would have the same layout as `Rc<[u16]>`). – darque Jul 28 '15 at 23:29
  • See also [Is it possible to create an Arc<[T\]> from a Vec?](https://stackoverflow.com/q/44636833/155423). – Shepmaster Sep 18 '17 at 13:03

2 Answers2

43

As of Rust 1.21.0 and as mandated by RFC 1845, creating an Rc<str> or Arc<str> is now possible:

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);
}

(Playground)

See <Rc as From<&str>> and <Arc as From<&str>>.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Centril
  • 2,549
  • 1
  • 22
  • 30
10

Creating an Rc<[T]> can be done via coercions and as-casts from fixed sized arrays, e.g. coercions can be done as follows:

use std::rc::Rc;

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

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

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

However, this doesn't work for strings, since they have no raw fixed-sized equivalent to create the first value. It is possible to do unsafely, e.g. by creating a UTF-8 encoded Rc<[u8]> and transmuting that to Rc<str>. Theoretically there could be a crate on crates.io for it, but I can't find one at the moment.

An alternative is owning_ref, which isn't quite std::rc::Rc itself, but should allow, for example, getting an RcRef<..., str> pointing into an Rc<String>. (This approach will work best if one uses RcRef uniformly in place of Rc, except for construction.)

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();
}

The erasing means that RcRef<..., str>s can come from multiple different sources, e.g. a RcRef<Erased, str> can come from a string literal too.

NB. at the time of writing, the erasure with RcRef requires a nightly compiler, and depending on owning_ref with the nightly feature:

[dependencies]
owning_ref = { version = "0.1", features = ["nightly"] }
huon
  • 94,605
  • 21
  • 231
  • 225
  • 1
    So a `Rc<[T]>` can't be created when the length of [T] is unknown at compile time, right? (But why is that? Isn't `Rc` allocated in the heap? since it's a fat pointer it already "knows" its size) – darque Jul 28 '15 at 23:41
  • Yeah, an `Rc<[T]>` can't be constructed via coercion with dynamic lengths. However the concept totally makes sense, the only blocking problem is that there's no good way to construct one. – huon Jul 28 '15 at 23:44
  • Also, would accessing the `String` inside `RcRef` require following two pointers? (the whole issue is that `Rc` has a pointer to `String` that has a pointer to the beginning of the string...). I can't figure out if [this](https://github.com/Kimundi/owning-ref-rs/blob/master/src/lib.rs#L178) just has a pointer to the `String`. – darque Jul 28 '15 at 23:46
  • Re: "the concept totally makes sense" - does it means that besides `Rc<[T]>`, `Rc` (and other unsized types) *could* work on a future version of Rust? – darque Jul 28 '15 at 23:47
  • @darque [it already can](http://is.gd/wWE4C6) for certain unsized types, like traits. This was new as of Rust 1.1 IIRC. – Shepmaster Jul 28 '15 at 23:55
  • @darque, `Rc` already does work: if you can make one (e.g. a `transmute` from UTF-8 `Rc<[u8]>` to `Rc`) everything will work fine, there's just no safe way to construct one directly. The `RcRef` implementation does only require a single pointer access to read the `str` data. – huon Jul 29 '15 at 00:54
  • Would it be possible to create a `Rc::clone_from(&T)` which would allow creating a `Rc` from a `&str`? – Matthieu M. Apr 27 '17 at 11:41
  • @MatthieuM. @darque It should be possible to create a `Rc<[T]>` given a `&[T]` using a compiler-blessed standard library function. This would require unsafe code and rely on a certain memory layout. While the heap allocated block is expected to store two reference counters followed by the data, the core Rust language doesn't guarantee any memory layout for `#[repr(Rust)]` types so far. So, providing something like this as an external crate is kind of dangerous. – sellibitze Aug 22 '17 at 13:26