Skip to content

Commit

Permalink
Add ResultTag tag and other minor changes
Browse files Browse the repository at this point in the history
Signed-off-by: Nick Cameron <nrc@ncameron.org>
  • Loading branch information
nrc committed Dec 6, 2021
1 parent 3296f89 commit 8aaedb4
Showing 1 changed file with 22 additions and 7 deletions.
29 changes: 22 additions & 7 deletions text/0000-dyno.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub mod error {

`get_context_ref` is added as a method on `Error` trait objects (`Error` is also likely to support similar methods for values and possibly other types, but I will elide those details), it simply calls `provide_any::request_by_type_tag` (we'll discuss this function and the `tags::Ref` passed to it below). But where does the context data come from? If a concrete error type supports backtraces, then it must override the `provide_context` method when implementing `Error` (by default, the method does nothing, i.e., no data is provided, so `get_context_ref` will always returns `None`). `provide_context` is used in the blanket implementation of `Provider` for `Error` types, in other words `Provider::provide` is delegated to `Error::provide_context`.

In `provide_context`, an error type provides access to data via a `Requisition` object, e.g., `req.provide_ref::<Backtrace>(&self.backtrace)`. The type of the reference passed to `provide_ref` is important here (and we encourage users to use explicit types with turbofish syntax even though it is not necessary). When a user calls `get_context_ref`, the requested type must match the type of an argument to `provide_ref`, e.g., the type of `&self.backtrace` is `&Backtrace`, so a call to `get_context_ref::<Backtrace>()` will return a reference to `self.backtrace`. An implementer can make multiple calls to `provide_ref` to provide multiple data with different types.
In `provide_context`, an error type provides access to data via a `Requisition` object, e.g., `req.provide_ref::<Backtrace>(&self.backtrace)`. The type of the reference passed to `provide_ref` is important here (and we encourage users to use explicit types with turbofish syntax even though it is not necessary, this might even be possible to enforce using a lint). When a user calls `get_context_ref`, the requested type must match the type of an argument to `provide_ref`, e.g., the type of `&self.backtrace` is `&Backtrace`, so a call to `get_context_ref::<Backtrace>()` will return a reference to `self.backtrace`. An implementer can make multiple calls to `provide_ref` to provide multiple data with different types.

Note that `Requisition` has methods for providing values as well as references, and for providing more complex types. These will be covered in the next section.

Expand Down Expand Up @@ -193,11 +193,18 @@ pub mod provide_any {
impl<'a, I: TypeTag<'a>> TypeTag<'a> for Optional<I> {
type Type = Option<I::Type>;
}

/// Tag combinator to wrap the given tag's value in an `Result<T, E>`
pub struct ResultTag<I, E>(PhantomData<I>, PhantomData<E>);

impl<'a, I: TypeTag<'a>, E: TypeTag<'a>> TypeTag<'a> for ResultTag<I, E> {
type Type = Result<I::Type, E::Type>;
}
}
}
```

For intermediate libraries, `Value` serves the common case of providing a new or temporary value (for example, computing a `String` message from multiple fields), `Ref` and `RefMut` cover the common case of providing a reference to a field of the providing object (as in `Error::context_ref` providing a reference to `self.backtrace` in the motivating examples), where the field does not contain non-static references. `Optional` is used in the implementation of `provide_any`, it is public since it seems like it could be generally useful.
For intermediate libraries, `Value` serves the common case of providing a new or temporary value (for example, computing a `String` message from multiple fields), `Ref` and `RefMut` cover the common case of providing a reference to a field of the providing object (as in `Error::context_ref` providing a reference to `self.backtrace` in the motivating examples), where the field does not contain non-static references. `Optional` is used in the implementation of `provide_any`, it is public since it seems like it could be generally useful. `ResultTag` is not strictly necessary, but is included as a complement to `Optional`.

### Requisition

Expand Down Expand Up @@ -231,12 +238,20 @@ impl<'a, 'b> Requisition<'a, 'b> {

`provide_value` and `provide_ref` are convenience methods for the common cases of providing a temporary value and providing a reference to a field of `self`, respectively. `provide_value` takes a function to avoid unnecessary work when querying for data of a different type; `provide_ref` does not use a function since creating a reference is typically cheap.

`provide` and `provide_with` offer full generality, but require the explicit use of type tags.
`provide` and `provide_with` offer full generality, but require the explicit use of type tags. An example of using `provide_with`:

It seems reasonable that data might be accessed and provided on different threads. For this purpose, `provide_any` includes a version of `Requisition` which implements `Send`: `SendRequisition`. An open question is if it is also useful to support `Sync` variations (and if there is a better name).
```rust
impl Error for MyError {
fn provide_context<'a>(&'a self, mut req: Requisition<'a, '_>) {
// Taking ownership of the string implies cloning it, so we use `provide_with` to avoid that
// operation unless it is necessary.
req.provide_with::<String>(|| self.suggestion.to_owned());
}
}
```

It seems reasonable that data might be accessed and provided on different threads. For this purpose, `provide_any` includes a version of `Requisition` which implements `Send`: `SendRequisition`. An open question is if it is also useful to support `Sync` variations (and if there is a better name).

TODO docs from crate

# Drawbacks
[drawbacks]: #drawbacks
Expand All @@ -257,9 +272,9 @@ Each of these approaches has significant downsides: adding methods to traits lea

`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`).
`provide_any` could be a module inside `any` rather than a sibling (it could then be renamed to `provide` or `provider`), or the contents of `provide_any` could be added to `any`.

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.

One hazard that must be avoided with such tweaks is that the API must distinguish between reference types with a `'static` lifetime and value types (with no lifetimes), either by using type tags or by having separate mechanisms for handling references and values. If this is done improperly, the API could be unsound. As an example, consider an API which lets an object provide a reference with type `&'a str` (where `'a` is the lifetime of some local scope), then a caller requests an object of type `&'static str` using an API designed for values (possible because that type satisfies the `'static` bound). Without some care, it is possible for the two types to be confused (because the lifetime is not reflected in the type tag or `TypeId`) and for the `&'a str` to be returned with the wrong lifetime. I believe this is not possible in the current proposal, but was possible in an earlier, more ergonomic iteration.

Expand Down

0 comments on commit 8aaedb4

Please sign in to comment.