8

Add provide_any module to core by nrc · Pull Request #3192 · rust-lang/rfcs · Gi...

 2 years ago
source link: https://github.com/rust-lang/rfcs/pull/3192
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Conversation

Copy link

Member

nrc commented 14 days ago

edited by m-ou-se

Rendered


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

Copy link

zkat commented 12 days ago

If it helps at all, this looks like it would be amaaaazing for miette. I believe that with this in place, miette could stop defining its own Diagnostic type and "simply" get users to provide various bits of context that it knows about. That means miette and its reporters would be able to use Eyre directly, instead of a bad fork of it :)

// Help text suggestion.

// Note, we should use a newtype here to prevent confusing different string context information.

if let Some(suggestion) = e.get_context_ref::<str>() {

Copy link

Contributor

@KodrAus KodrAus 8 days ago

I think it might be good to see this actually fleshed out properly here. It hints at a feature of this scheme that might benefit from some stronger up-front guidance.

In the accessor method approach you have a closed set of possible things to access along with what those things look like. That has its own upsides and downsides. Here, it’s more convention-driven, where instead of being offered a closed set of methods like backtrace or suggestion you agree on some of an open set of types, like Backtrace or Suggestion. In that convention-based approach any non-semantic types like str seem meaningless to provide or ask for.

`provide_any` could live in its own crate, rather than in libcore. However, this would not be useful for `Error`.

`provide_any` could be a module inside `any` rather than a sibling (it could then be renamed to `provide` or `provider`).

Copy link

Contributor

@KodrAus KodrAus 8 days ago

Living under any rather than in a new top-level module seems like a more natural home for this to me. There's overlap between type tags and type ids, and Any and Provider that I think we could give proper treatment together under the same umbrella.

Copy link

Member

Author

@nrc nrc 7 days ago

that makes sense! I'm a bit hesitant because type ids and type tags are so obviously similar but have nothing in common in their interfaces or how they are used, so I thought it might be a bit confusing to have them together

Copy link

Contributor

@KodrAus KodrAus 3 days ago

Do you think trying to come up with a module-level doc for a hypothetical combined any module might be a good exercise for identifying those cases where that confusion can come from?

The `TypeTag` trait is an abstraction over all type tags. It does not have any methods, only an associated type for the type which the tag represents. E.g., `<Ref<T> as TypeTag<'a>>::Type` is `&'a T`.

There is no universal type tag. A concrete type tag must be written for a 'category' of types. A few common tags are provided in `provide_any::tags`, including `Value` for any type bounded by `'static`, and `Ref` for types of the form `&'a T` where `T: 'static`. For less common types, the user must provide a tag which implements `TypeTag`; in this way the `provide_any` API generalises to all types.

Why is Ref not a type tag combinator, where if T has a type tag then Ref can be used as type tag for references to T (somewhat similar to the Option combinator that does seem to exist)?

}

/// Tag combinator to wrap the given tag's value in an `Option<T>`

pub struct Optional<I>(PhantomData<I>);

Copy link

Contributor

@teor2345 teor2345 5 days ago

Along with the Optional combinator, can we have a Resultant combinator?

It seems weird to support Option-wrapping, but not Result-wrapping. And it would also help illustrate the expressive power of the type tag design.

`provide_any` could be a module inside `any` rather than a sibling (it could then be renamed to `provide` or `provider`).

There are numerous ways to tweak the API of a module like `provide_any`. The dyno and object-provider crates provide two such examples. There are many others, for example providing more patterns of types without requiring tags, not providing any common type patterns (i.e., always requiring tags), not exposing tags at all, etc.

Copy link

Contributor

@teor2345 teor2345 5 days ago

Nitpick: quote crate names for readability and consistency

Suggested change
There are numerous ways to tweak the API of a module like `provide_any`. The dyno and object-provider crates provide two such examples. There are many others, for example providing more patterns of types without requiring tags, not providing any common type patterns (i.e., always requiring tags), not exposing tags at all, etc. There are numerous ways to tweak the API of a module like `provide_any`. The `dyno` and `object-provider` crates provide two such examples. There are many others, for example providing more patterns of types without requiring tags, not providing any common type patterns (i.e., always requiring tags), not exposing tags at all, etc.

Copy link

Contributor

KodrAus commented 3 days ago

I found a nice usecase for something like this in my day-to-day codebase, which is a storage engine, that I thought I'd share. Individual documents can be cached for better performance. Caching is fine-grained, so for each document we can choose a different caching strategy. We've got a trait for these cache entries that looks like this:

pub trait CacheEntry: Any + Send + Sync + Display {
    /**
    The raw on-disk payload bytes.
    */
    fn bytes_from_disk(&self) -> Option<BytesFromDisk> {
        None
    }

    /**
    The payload bytes that are yielded to callers.

    This may involve decompression or other transformations over the raw on-disk payload.
    */
    fn bytes(&self) -> Option<Bytes> {
        None
    }

    /**
    The approximate size of this entry in-memory.
    */
    fn approximate_size(&self) -> usize;
}

The combination of Any as a supertrait plus some optional getters are a pretty clear smell for Provider. The reason this type is Any at all is so different layers of the storage engine can cache differently shaped things, like a fully deserialized value for frequently accessed values. The Any trait just gives us a single extensibility point that only supports a single specialized usecase at a time, with Provider we'd simply provide another kind of thing a caller can ask for.

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK