Skip to content

Tracking Issue for Provider API #96024

Description

@yaahc

View all comments

Feature gate: #![feature(provide_any)]

This is a tracking issue for RFC 3192

This RFC proposes extending the any module of the core library with a generic API for objects to provide type-based access to data. (In contrast to the existing APIs which provides type-driven downcasting, the proposed extension integrates downcasting into data access to provide a safer and more ergonomic API).

The initial motivating use case for this API is to provide the necessary functionality to add generic member access to the Error trait to generalize extracting context like Backtrace and to make it possible to move the Error trait into core while leaving Backtrace in std.

Public API

// core::any
pub trait Provider {
    fn provide<'a>(&'a self, demand: &mut Demand<'a>);
}

pub fn request_value<'a, T>(provider: &'a (impl Provider + ?Sized)) -> Option<T>
where
    T: 'static;

pub fn request_ref<'a, T>(provider: &'a (impl Provider + ?Sized)) -> Option<&'a T>
where
    T: 'static + ?Sized;

#[repr(transparent)]
pub struct Demand<'a>(_);

impl<'a> Demand<'a> {
    pub fn provide_value<T>(&mut self, value: T) -> &mut Self
    where
        T: 'static;

    pub fn provide_value_with<T>(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self
    where
        T: 'static;

    pub fn provide_ref<T: ?Sized + 'static>(&mut self, value: &'a T) -> &mut Self;

    pub fn provide_ref_with<T: ?Sized + 'static>(
        &mut self,
        fulfil: impl FnOnce() -> &'a T,
    ) -> &mut Self;

    pub fn would_be_satisfied_by_value_of<T>(&self) -> bool
    where
        T: 'static;

    pub fn would_be_satisfied_by_ref_of<T>(&self) -> bool
    where
        T: ?Sized + 'static,
}

impl<'a> fmt::Debug for Demand<'a> {}

// core::error
impl<E> Provider for E
where
    E: Error + ?Sized;

Steps / History

  • Implementation: Add the Provider api to core::any #91970
  • Delete the Provider trait and merge its functionality into Error.
  • Pick either demand or request and use that name consistently.
  • LLVM is unable to optimize multiple .provide_* calls into a switch table because each call fetches the type id from Erased::type_id separately each time and the compiler doesn't know that these calls all return the same value. This should be fixed. See Add provide_any module to core rfcs#3192 (comment) for one possible solution (although alternative solutions may be possible).
  • Final comment period (FCP)
  • Stabilization PR

Unresolved Questions

  • Should the APIs be named demand_* or request_*?

Activity

  1. added
    T-libs-api[DEPRECATED; DO NOT USE]
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.
    on Apr 13, 2022
  2. yaahc commented on Jul 8, 2022

    @yaahc
    MemberAuthor

    #98912 updates the request_* functions to have an APIT (arg position impl trait) parameter instead of a standard generic parameter to remove unnecessary inferred parameters while turbofishing.

    Callers change:

    let obj: Box<dyn Provider> = Box::new(SomeConcreteType { some_string: "hello".to_owned() });
    
    // From
    assert_eq!(&**request_ref::<String, _>(&*obj).unwrap(), "hello");
    
    // To
    assert_eq!(&**request_ref::<String>(&*obj).unwrap(), "hello");
  3. dtolnay commented on Aug 31, 2022

    @dtolnay
    Member

    One minor thing that weirds me out about this API is this impl:

    impl<E> Provider for E
    where
    E: Error + ?Sized,

    Since this is a blanket impl (Self type is a generic parameter), it is the one blanket impl that Provider is ever going to be able to have, at least absent some far-future negative impls feature in the language. Any other blanket impl would be considered overlapping and violate coherence.

    But it's a blanket impl of core::any::Provider, which is envisioned as being as general-purpose as the Any trait, for types which implement core::error::Error, which is a much narrower use case. In some sense this relationship is saying that "Errors are the one true canonical/privileged implementers of the Provider trait; any other application of Provider is second-class".

    I am wondering which one of the following would be feasible:

    1. Yes, errors are the one privileged application of Provider, and while Provider may be useful for some other things sometimes, all of those are second-class.

      a. Maybe move Provider to core::error to double down on this view?

    2. Remove the blanket impl. What are the things that make the blanket impl necessary for usability of this API, and can those be mitigated a different way?

  4. yaahc commented on Aug 31, 2022

    @yaahc
    MemberAuthor

    We could go back to what we had before we move Error into core which was a provide impl just on dyn Error which caused some other paper cuts but I can't remember exactly what those issues were.

  5. thomcc commented on Aug 31, 2022

    @thomcc
    Member

    Another issue for the provider API that matters for non-error cases is the lack of support for &mut.

    I think the initial proposal did support this, at some cost to complexity. While I don't know that we need to support it out of the gate (I've wanted it multiple times when playing around with the API), it would be good to be sure we know it would look like, so that if we want to add it in the future, that we have not painted ourselves into a corner where we cannot do so.

  6. yaahc commented on Aug 31, 2022

    @yaahc
    MemberAuthor

    @thomcc I believe all we have to do is expose the tag APIs to support those cases. We didn't change the impl, only the exposed API.

  7. added a commit that references this issue on Sep 1, 2022
  8. added 6 commits that reference this issue on Sep 1, 2022
  9. 62 remaining items

  10. waynr commented on Jul 29, 2023

    @waynr
    Contributor

    @yaahc I think we've captured everything but

    LLVM is unable to optimize multiple .provide_* calls into a switch table because each call fetches the type id from Erased::type_id separately each time and the compiler doesn't know that these calls all return the same value. This should be fixed. See Add provide_any module to core rfcs#3192 (comment) for one possible solution (although alternative solutions may be possible).

    in the TODOs for the error_generic_member_access tracking issue so we should be able to close this tracking issue, right?

  11. added a commit that references this issue on Aug 1, 2023
  12. added a commit that references this issue on Aug 14, 2023
  13. jmjoy commented on Aug 18, 2023

    @jmjoy

    How about add an optional key? Just like:

    pub fn provide_value<T>(&mut self, key: Option<&str>, value: T) -> &mut Demand<'a>;

    then we can provide multi value with the same type, such as:

    struct Sth {
        foo: String,
        bar: String,
    }
    
    impl Provider for Sth {
        fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
            demand
                .provide_value::<String>(Some("foo"), self.foo)
                .provide_value::<String>(Some("bar"), self.bar);
        }
    }
  14. waynr commented on Aug 18, 2023

    @waynr
    Contributor

    FEI, the Provider API has been rejected by the libs meeting folks. For more background, please see my earlier comment in this issue: #96024 (comment)

    The TL;DR is that the Provider trait has been rejected, leaving just the Request type (aka the type formerly known as Demand) and the new provide method on the Error trait. The Request type and its associated internal-only machinery has been moved from the core::any module into core::error. This is the state of affairs in latest nightly after #113464 was merged earlier this week.

    The only thing that's left to do as far as this tracking issue is concerned is to close it (as far as I understand).

  15. TennyZhuang commented on Sep 5, 2023

    @TennyZhuang
    Contributor

    Is there any third-party crate to maintain the Provider trait in the future? Although it may not be suitable to be merged in the std::any, it's still a useful pattern in some scenarios.

  16. waynr commented on Sep 5, 2023

    @waynr
    Contributor

    @TennyZhuang the closest thing I know of was mentioned in the notes for this feature's RFC, which can be found here: provide-any

    I don't know how similar this is to what was unstable here in the standard library and there are no published crates.

  17. conradludgate commented on Sep 5, 2023

    @conradludgate
    Contributor

    There is also the supplier crate that I made in response to this feature being removed. I also consider it unstable but it's a direct lift and shift from the latest provider impl that std had in nightly

    https://docs.rs/supplier/0.0.2/supplier/

    I had plans to change the API but all my attempts so far have failed to be beneficial

  18. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.B-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.B-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions