Skip to content

ACP: Remove 'static from Any itself, moving the bound to the operations themselves #819

Description

@orlp

Proposal

Problem statement

The Any trait has a 'static bound. This is unnecessarily restrictive. It means you can't add Any as a supertrait to your own traits if you wish to support downcasting, unless you already have a 'static bound yourself.

The trait would be more useful if we push this bound to the relevant operations/functions, rather than the trait itself.

Motivating examples or use cases

The immediate motivator for this ACP is Error. There has been an effort to add type_id to dyn Error (rust-lang/rust#60784). If you look at that issue, and at a large amount of the implementations on Error, we are essentially just re-implementing Any on Error. This is because we can't simply add an Any bound to Error because of the 'static bound, as there might be Error types which contain lifetimes. This problem applies to any trait which is intended to be used in dyn while also being intended for things containing lifetimes.

If we move the 'static bound to the relevant methods/associated functions instead of the trait itself, we solve the above problem and can simply add Any as a supertrait to Error. Then Error + 'static automatically gets type_id. In general with this change one can freely add Any as a supertrait to support downcasting.

Future use cases (not proposed here)

But, should we take this first step, we also unlock the capability for future enhancements for the language in general:

  1. If we remove the 'static bound, everything and anything implements Any. This means it might be possible in the future to make Any an 'auto-supertrait', always implied by any dyn Trait. This means you can always downcast from dyn Trait, even if no one bothered to specify Any explicitly. The cost of this is slightly enlarging every single dyn vtable with the information needed to downcast. C++ makes this choice, for example.

  2. We can possibly relax the 'static bound away from TypeId::of entirely in the future. If so then you can request the type id of any dyn Trait where Trait : Any (and if we do the previous suggestion that means, always). This can be quite nice for debugging. Note that this relaxation only applies to querying a type id, not to downcasting, and that there is a previously-closed RFC to remove this bound: https://github.com/rust-lang/rfcs/blob/master/text/1849-non-static-type-id.md.

  3. Safe downcasting from dyn Any + 'a, not just dyn Any + 'static. For some types you can soundly downcast to it from dyn Any + 'a for any 'a, as the TypeId matching lets us conclude there is no lifetime issue. This includes types such as u32 and struct Foo { name: &'static str } but not &'static str itself (as it's indistinguishable from &'a str) - the exact requirements requires more investigation.

I would like to emphasize though that these enhancements are not proposed by this ACP, and are entirely optional. However without this change they are essentially impossible.

Solution sketch

My suggestion is that starting immediately, we add an explicit 'static bound anywhere Any is used in the standard library. E.g. the Any trait itself immediately becomes:

pub trait Any: 'static {
    fn type_id(&self) -> TypeId
    where Self: 'static;
}

and for example downcast_ref explicitly adds a 'static bound to both dyn Any and T: Any:

impl dyn Any + 'static {
    pub fn downcast_ref<T: Any + 'static>(&self) -> Option<&T> { ... }
}

Then, in the next edition of Rust, we remove the Any: 'static bound from the Any trait itself, and add it as a supertrait of Error (and possibly others).

If a crate is compiled with edition <= 2024, Any becomes a synonym for Any + 'static. This is needed for backwards compatibility, old code might be reliant on the fact that Any implies 'static. If a crate is compiled with edition > 2024, Any simply means Any without any lifetime bound.

Alternatives

A potential alternative to this would be to introduce a new trait which is effectively "Any without 'static". This would be backwards compatible without an edition break but would ultimately be more disruptive for the ecosystem as one might have dyn Any but an API expects dyn NoStaticAnyBikeshed. The other alternative would be to do nothing, and re-implement effectively all of Any onto Error as well as any other traits meant to support both lifetimes and dyn downcasting.

Links and related work

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libsRelevant to the libraries subteam, which will review and decide on the PR/issue.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