rust-lang / libs-team

The home of the library team
Apache License 2.0
123 stars 19 forks source link

API {Arc, Rc}::from_static #400

Closed NobodyXu closed 3 months ago

NobodyXu commented 3 months ago

Proposal

Problem statement

Sometimes we'd like to create an Arc or Rc from a literal value known at compile-time.

Providing an API for it would make it possible for Rc or Arc to be constructed at compile time, reduce memory usage and speedup cloning and dropping.

I took the inspiration from python's new optimization, where common value are pre-allocated and frozen, inc and dec are no-op.

Motivating examples or use cases

static ARC: Arc<str> = Arc::from_static("1");

Solution sketch

pub struct  ArcStaticStorage<T: ?Sized>(ArcInner<T>);

impl<T: ?Sized> ArcStaticStorage<T> {
    const fn new(data: T) -> Self -> {
        Self(ArcInner {
            strong: AtomicUsize::new(usize::MAX), // special value, never incremented or decremented
            weak: AtomicUsize::new(usize::MAX),
            data,
        })
    }
}

impl<T: ?Sized> Arc<T> {
    const fn from_static(storage: &'static ArcStaticStorage<T>) -> Self;
}
RustyYato commented 3 months ago

Previous discussion:

https://internals.rust-lang.org/t/immortal-arc-api/19738

axelkar commented 3 months ago

Can't you just replace Arc with AoR? How I see this is that you want to use &'static str where the preexisting API only allows Arc<str>? I imagine that this would affect the performance of dropping and cloning Arcs since you have to check for the special value.

NobodyXu commented 3 months ago

Can't you just replace Arc with AoR?

Thanks that's indeed doable, though it would complicate the typing.

I imagine that this would affect the performance of dropping and cloning Arcs since you have to check for the special value.

Well, I imagine an atomic write is much more expensive than a branch, since it would involve invalidation of any data sitting in the same cache as the atomic, for example, the data stored in Arc.

For Rc, you could avoid that branch by using conditional move.

pitaj commented 3 months ago

As mentioned in the IRLO thread, the best way to achieve this is to make the receiver function generic and capable of taking &'static T or Arc<T>. Quoting CAD97:

A "zero overhead" solution is to change the APIs from taking Arc<Ty> to impl P<Ty> for some trait P<T> = 'static + Deref<Target=T> + Clone + Send + Sync. (Use a trait with blanket impl instead of a trait alias for stable.) Then any such APIs can be monomorphized for Arc<T> or &'static T.

This essentially describes a trait AoR which does the handling at compile-time instead of runtime. Example:

use std::ops::Deref;
use std::sync::Arc;

trait AoS<T: ?Sized>: 'static + Deref<Target=T> + Clone + Send + Sync {}
impl<T: ?Sized, A> AoS<T> for A
where
    A: 'static + Deref<Target=T> + Clone + Send + Sync
{}

fn str_len(s: impl AoS<str>) -> usize {
    s.len()
}

fn main() {
    static ONE: &str = "1";
    let abcdef = Arc::from("abcdef");

    dbg!(str_len(ONE));
    dbg!(str_len(abcdef));
}

playground

NobodyXu commented 3 months ago

Thinking about this feature again, I think using an enum or a generic does make more sense.