Summary
This is an RFC to add the APIs: From<&[T]> for Rc<[T]> where T: Clone or T: Copy as well as From<&str> for Rc<str>. In addition: From<Vec<T>> for Rc<[T]> and From<Box<T: ?Sized>> for Rc<T> will be added.
Identical APIs will also be added for Arc.
Motivation
Caching and string interning
These, and especially the latter - i.e: From<&str>, trait implementations of From are useful when dealing with any form of caching of slices.
This especially applies to controllable string interning, where you can cheaply cache strings with a construct such as putting Rcs into HashSets, i.e: HashSet<Rc<str>>.
An example of string interning:
use Rc;
use HashSet;
use drop;
let first = "hello world!";
let second = "goodbye!";
let mut set = new;
// Cache the slices:
let rc_first = cache_str;
let rc_second = cache_str;
let rc_third = cache_str;
// The contents match:
assert_eq!;
assert_eq!;
assert_eq!;
// It was cached:
assert_eq!;
drop;
assert_eq!;
assert_eq!;
assert_eq!;
assert!;
One could imagine a scenario where you have an AST with string literals that gets repeated a lot in it. For example, namespaces in XML documents tends to be repeated many times.
The tendril crate does one form of interning:
Buffer sharing is accomplished through thread-local (non-atomic) reference counting
It is useful to provide an implementation of From<&[T]> as well, and not just for &str, because one might deal with non-utf8 strings, i.e: &[u8]. One could potentially reuse this for Path, OsStr.
Safe abstraction for unsafe code.
Providing these implementations in the current state of Rust requires substantial amount of unsafe code. Therefore, for the sake of confidence in that the implementations are safe - it is best done in the standard library.
RcBox is not public
Furthermore, since RcBox is not exposed publicly from std::rc, one can't make an implementation outside of the standard library for this without making assumptions about the internal layout of Rc. The alternative is to roll your own implementation of Rc in its entirety - but this in turn requires using a lot of feature gates, which makes using this on stable Rust in the near future unfeasible.
For Arc
For Arc the synchronization overhead of doing .clone() is probably greater than the overhead of doing Arc<Box<str>>. But once the clones have been made, Arc<str> would probably be cheaper to dereference due to locality.
Most of the motivations for Rc applies to Arc as well, but the use cases might be fewer. Therefore, the case for adding the same API for Arc is less clear. One could perhaps use it for multi threaded interning with a type such as: Arc<Mutex<HashSet<Arc<str>>>>.
Because of the similarities between the layout of Rc and Arc, almost identical implementations could be added for From<&[T]> for Arc<[T]> and From<&str> for Arc<str>. It would also be consistent to do so.
Taking all of this into account, adding the APIs for Arc is warranted.
Detailed design
There's already an implementation
There is already an implementation of sorts alloc::rc for this. But it is hidden under the feature gate rustc_private, which, to the authors knowledge, will never be stabilized. The implementation is, on this day, as follows:
The idea is to use the bulk of the implementation of that, generalize it to Vecs and slices, specialize it for &str, provide documentation for both.
Copy and Clone
For the implementation of From<&[T]> for Rc<[T]>, T must be Copy if ptr::copy_nonoverlapping is used because this relies on it being memory safe to simply copy the bits over. If instead, T::clone() is used in a loop, then T can simply be Clone instead. This is however slower than using ptr::copy_nonoverlapping.
Vec and Box
For the implementation of From<Vec<T>> for Rc<[T]>, T need not be Copy, nor Clone. The input vector already owns valid Ts, and these elements are simply copied over bit for bit. After copying all elements, they are no longer
owned in the vector, which is then deallocated. Unfortunately, at this stage, the memory used by the vector can not be reused - this could potentially be changed in the future.
This is similar for Box.
Suggested implementation
The actual implementations could / will look something like:
For Rc
unsafe
These work on zero sized slices and vectors as well.
With more safe abstractions in the future, this can perhaps be rewritten with less unsafe code. But this should not change the API itself and thus will never cause a breaking change.
For Arc
For the sake of brevity, just use the implementation above, and replace:
slice_to_rcwithslice_to_arc,RcBoxwithArcInner,rcbox_ptrwitharcinner_ptr,RcwithArc.
How We Teach This
The documentation provided in the impls should be enough.
Drawbacks
The main drawback would be increasing the size of the standard library.
Alternatives
- Only implement this for
T: Copyand skipT: Clone. - Let other libraries do this. This has the problems explained in the motivation
section above regarding
RcBoxnot being publicly exposed as well as the amount of feature gates needed to roll ones ownRcalternative - for little gain. - Only implement this for
Rcand skip it forArc. - Skip this for
Vec. - Only implement this for
Vec. - Skip this for
Box. - Use
AsRef. For example:impl<'a> From<&'a str> for Rc<str>becomesimpl From<AsRef<str>> for Rc<str>. It could potentially make the API a bit more ergonomic to use. However, it could run afoul of coherence issues, preventing other wanted impls. This RFC currently leans towards not using it. - Add these trait implementations of
Fromas functions on&strlike.into_rc_str()and on&[T]like.into_rc_slice(). This RFC currently leans towards usingFromimplementations for the sake of uniformity and ergonomics. It also has the added benefit of letting you remember one method name instead of many. One could also considerString::into_boxed_strandVec::into_boxed_slice, since these are similar with the difference being that this version uses theFromtrait, and is converted into a shared smart pointer instead. - Also add these APIs as
associated functionsonRcandArcas follows:
Unresolved questions
- Should a special version of
make_mutbe added forRc<[T]>? This could look like: