Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build an Rc<str> or Rc<[T]>?

Tags:

rust

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).

like image 807
darque Avatar asked Jul 28 '15 19:07

darque


2 Answers

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>>.

like image 122
Centril Avatar answered Nov 20 '22 02:11

Centril


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"] }
like image 10
huon Avatar answered Nov 20 '22 03:11

huon