Skip to content

ACP: Add a downcasting method for Arc<dyn Any> #862

Description

@maxdexh

Proposal

Problem statement

Currently, we have downcast methods for Box<dyn Any>, Box<dyn Any + Send>, Box<dyn Any + Send + Sync>, Rc<dyn Any>, and Arc<dyn Any + Send + Sync>, but we are missing it for Arc<dyn Any>.

Note that it is perfectly reasonable to store non-Send + Sync data inside an Arc, as it still gives one shared type for generic storage, and later downcasting/dynamic dispatch can recover the Send + Sync bound as needed.

Motivating examples or use cases

Real-World code-patterns of where Arc::<dyn Any>::downcast would have been useful:

Note that the latter two patterns (cloning the data from behind downcast_ref) lead to code using Arc<dyn Any> to ensure the type-erased data can be cloned around, but then failing to retrieve a long-lived version of the data cheaply. This partially defeats the purpose of using Arc in the first place.

From personal experience, I have seen (and written) code that avoids the "retrieval clone" by wrapping the data itself inside another Arc, thus leading to unnecessary double indirection and code complexity. This may even be unavoidable if the type-erased data is something that can't/shouldn't be cloned, such as a Mutex.

Solution sketch

I propose a new downcasting method for Arc<dyn Any> named downcast_local. See the "Alternatives" section for why it might not be possible for this method to be named downcast.

impl<A: Allocator> Arc<dyn Any, A> {
    pub fn downcast_local<T: Any>(self) -> Result<Arc<T>, Self> { ... }
}

Alternatives

One alternative is not doing anything; thus leaving users to write inefficient and overcomplicated code, or to implement the functionality themselves.

We may also consider using the traditional downcast name, instead of the proposed downcast_local, to mimic the way that the Box::downcast methods all share a name.
Note however that since Arc::<dyn Any + Send + Sync>::downcast is currently the only method with that name, adding Arc::<dyn Any>::downcast would add a possible coercion target, thus breaking the following code:

trait MySubtrait: std::any::Any {}
fn downcast_my_subtrait(x: Arc<dyn MySubtrait + Send + Sync>) -> Option<Arc<i32>> {
    // `x` implicitly coerces to `Arc<dyn Any + Send + Sync>`.
    // This would error with "multiple applicable items in scope" if we added `Arc::<dyn Any>::downcast`,
    // as can be seen in analogous code with `Box::downcast`! 
    Arc::downcast(x).ok()
}

I.e. upcasting coercions from Arc<dyn SubtraitOfAnySendSync> to Arc<dyn Any + Send + Sync> in argument position would break when calling downcast using associated function syntax. The impact would have to be assessed early, as I believe the breakage would be insta-observable in stable code, even if added as #[unstable].
The advantages of this would be better consistency and a clearer path towards adding methods like Arc::<dyn Error (+ ...)>::downcast, which would otherwise require yet another name.

Links and related work

  • Related discussion on zulip.
  • PR that introduced Arc::downcast

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions