From 39fc23ec55b738780c5f325c497736513fe58162 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 1 Apr 2020 16:37:56 -0700 Subject: [PATCH 01/40] Initial commit for first rfc --- text/0000-dyn-error-generic-member-access.md | 304 +++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 text/0000-dyn-error-generic-member-access.md diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md new file mode 100644 index 00000000000..d2b16a6cf6d --- /dev/null +++ b/text/0000-dyn-error-generic-member-access.md @@ -0,0 +1,304 @@ +- Feature Name: Add fns for generic member access to dyn Error and the Error trait +- Start Date: 2020-04-01 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This RFC proposes a pair of additions to the `Error` trait to support accessing +generic forms of context from `dyn Error` trait objects, one method on the +`Error` trait itself for returning references to members based on a given +typeid, and another fn implemented for `dyn Error` that uses a generic return +type to get the type id to pass into the trait object's fn. These functions +will act as a generalized version of `backtrace`, `source`, and `cause`, and +would primarily be used during error reporting when rendering a chain of opaque +errors. + +# Motivation +[motivation]: #motivation + +Today, there are a number of forms of context that are traditionally gathered +when creating errors. These members are gathered so that a final error +reporting type or function can access them and render them independently of the +`Display` implementation for that specific error type to allow for consistently +formatted and flexible error reports. Today, there are 2 such forms of context +that are traditionally gathered, `backtrace` and `source`. + +However, the current approach of promoting each form of context to a fn on the +`Error` trait doesn't leave room for forms of context that are not commonly +used, or forms of context that are defined outside of the standard library. + +By adding a generic form of these functions that works around the issues of +monomorphization on trait objects we can support more forms of context and +forms of context that are experimented with outside of the standard library +such as: + +* `SpanTrace` a backtrace like type from the `tracing-error` library +* zig-like Error Return Traces by extracting `Location` types from errors + gathered via `#[track_caller]` +* error source trees instead of chains by accessing the source of an error as a + slice of errors rather than as a single error, such as a set of errors caused + when parsing a file + +With a generic form of member access available on `Error` trait objects we +could support a greater diversity of error handling needs and make room for +experimentation on new forms of context in error reports. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +When implementing error handling in rust there are two main aspects that should +be considered, the creation of errors and the reporting of errors. + +Error handling in rust consists mainly of two steps, creation/propogation and +reporting. The `std::error::Error` trait exists to bridge this gap. It does so +by acting as a consistent interface that all Error types can implement to allow +Error Reporting types to handle them in a consistent manner when constructing +reports for end users. + +The error trait accomplishes this by providing a set of methods for accessing +members of `dyn Error` trait objects. For accessing the message that should be +rendered to the end user the Error trait implements the `Display` trait. For +accessing `dyn Error` members it provides the `source` function, which +conventionally represents the lower level error that caused a subsequent error. +For accessing a `Backtrace` of the state of the stack when an error was created +it provides the `backtrace` function. For all other forms of context relevant +to an Error Report the error trait provides the `context`/`context_any` +functions. + +As an example lets explore how one could implement an error reporting type that +retrieves the Location where each error in the chain was created, if it exists, +and renders it as part of the chain of errors. + +The goal is to implement an Error Report that looks something like this: + +``` +Error: + 0: Failed to read instrs from ./path/to/instrs.json + at instrs.rs:42 + 1: No such file or directory (os error 2) +``` + +The first step is to define or use a Location type. In this example we will +define our own but we could use also use `std::panic::Location` for example. + +```rust +struct Location { + file: &'static str, + line: usize, +} +``` + +Next we need to gather the location when creating our error types. + +```rust +struct ExampleError { + source: std::io::Error, + location: Location, + path: PathBuf, +} + +impl fmt::Display for ExampleError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "Failed to read instrs from {}", path.display()) + } +} + +fn read_instrs(path: &Path) -> Result { + std::fs::read_to_string(path).map_err(|source| { + ExampleError { + source, + path: path.to_owned(), + location: Location { + file: file!(), + line: line!(), + }, + } + }) +} +``` + +Next we need to implement the `Error` trait to expose these members to the +Error Reporter. + +```rust +impl std::error::Error for ExampleError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } + + fn context_any(&self, type_id: TypeID) -> Option<&dyn Any> { + if id == TypeId::of::() { + Some(&self.location) + } else { + None + } + } +} +``` + +And finally, we create an error reporter that prints the error and its source +recursively along with the location data if it was gathered. + +```rust +struct ErrorReporter(Box); + +impl fmt::Debug for ErrorReporter { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut current_error = Some(self.0.as_ref()); + let mut ind = 0; + + while let Some(error) = current_error { + writeln!(fmt, " {}: {}", ind, error)?; + + if let Some(location) = error.context::() { + writeln!(fmt, " at {}:{}", location.file, location.line)?; + } + + ind += 1; + current_error = error.source(); + } + + Ok(()) + } +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +There are two additions necessary to the standard library to implement this +proposal: + + +First we need to add a function for dyn Error trait objects that will be used +by error reporters to access members given a generic type. This function +circumvents restrictions on generics in trait functions by being implemented +for trait objects only, rather than as a member of the trait itself. + +```rust +impl dyn Error { + pub fn context(&self) -> Option<&T> { + self.context_any(TypeId::of::())?.downcast_ref::() + } +} +``` + +Second we need to add a member to the `Error` trait to provide the `&dyn Any` +trait objects to the `context` fn for each member based on the type_id. + +```rust +trait Error { + /// ... + + fn context_any(&self, id: TypeId) -> Option<&dyn Any> { + None + } +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +* The API for defining how to return types is cumbersome and possibly not + accessible for new rust users. + * If the type is stored in an Option getting it converted to an `&Any` will + probably challenge new devs, this can be made easier with documented + examples covering common use cases and macros like `thiserror`. +```rust +} else if typeid == TypeId::of::() { + self.span_trace.as_ref().map(|s| s as &dyn Any) +} +``` +* When you return the wrong type and the downcast fails you get `None` rather + than a compiler error guiding you to the right return type, which can make it + challenging to debug mismatches between the type you return and the type you + use to check against the type_id + * The downcast could be changed to panic when it fails + * There is an alternative implementation that mostly avoids this issue +* Introduces more overhead from the downcasts +* This approach cannot return slices or trait objects because of restrictions + on `Any` + * The alternative solution avoids this issue + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The two alternatives I can think of are: + +## Do Nothing + +We could not do this, and continue to add accessor functions to the `Error` +trait whenever a new type reaches critical levels of popularity in error +reporting. + + +## Use an alternative to Any for passing generic types across the trait boundary + +Nika Layzell has proposed an alternative implementation using a `Provider` type +which avoids using `&dyn Any`. I do not necessarily think that the main +suggestion is necessarily better, but it is much simpler. + * https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b + * https://github.com/mystor/object-provider + +With this design an implementation of the `context_any` fn might instead look like: + +```rust +fn provide<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + request + .provide::(&self.path)? + .provide::(&self.path)? + .provide::(&self.path) +} +``` + +The advantages of this design are that: + +1. It supports accessing trait objects and slices +2. If the user specifies the type they are trying to pass in explicitly they + will get compiler errors when the type doesn't match. +3. Less verbose implementation + +The disadvatages are: + +1. More verbose function signature, very lifetime heavy +2. The Request type uses unsafe code which needs to be verified +3. could encourage implementations where they pass the provider to + `source.provide` first which would prevent the error reporter from knowing + which error in the chain gathered each piece of context and might cause + context to show up multiple times in a report. + +# Prior art +[prior-art]: #prior-art + +I do not know of any other languages whose error handling has similar +facilities for accessing members when reporting errors. For the most part prior +art exists within rust itself in the form of previous additions to the `Error` +trait. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What should the names of these functions be? + - `context`/`context_ref`/`context_any` + - `member`/`member_ref` + - `provide`/`request` +- Should we go with the implementation that uses `Any` or the one that supports + accessing dynamically sized types like traits and slices? +- Should there be a by value version for accessing temporaries? + - I bring this up specifically for the case where you want to use this + function to get an `Option<&[&dyn Error]>` out of an error, in this case + its unlikely that the error behind the trait object is actually storing + the errors as `dyn Errors`, and theres no easy way to allocate storage to + store the trait objects. + +# Future possibilities +[future-possibilities]: #future-possibilities + +I'd love to see the various error creating libraries like `thiserror` adding +support for making members exportable as context for reporters. + +Also, I'm interested in adding support for `Error Return Traces`, similar to +zigs, and I think that this accessor function might act as a critical piece of +that implementation. From 63d0539ed0706bf494b129c47b435ab1ee19eeb7 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 1 Apr 2020 17:11:54 -0700 Subject: [PATCH 02/40] First wave of edits --- text/0000-dyn-error-generic-member-access.md | 39 +++++++++++--------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d2b16a6cf6d..e21f4c9c2d4 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -1,6 +1,6 @@ - Feature Name: Add fns for generic member access to dyn Error and the Error trait - Start Date: 2020-04-01 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/2895) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary @@ -8,12 +8,11 @@ This RFC proposes a pair of additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects, one method on the -`Error` trait itself for returning references to members based on a given -typeid, and another fn implemented for `dyn Error` that uses a generic return -type to get the type id to pass into the trait object's fn. These functions -will act as a generalized version of `backtrace`, `source`, and `cause`, and -would primarily be used during error reporting when rendering a chain of opaque -errors. +`Error` trait itself for returning references to members based on a given type +id, and another fn implemented for `dyn Error` that uses a generic return type +to get the type id to pass into the trait object's fn. These functions will act +as a generalized version of `backtrace` and `source`, and would primarily be +used during error reporting when rendering a chain of opaque errors. # Motivation [motivation]: #motivation @@ -21,18 +20,15 @@ errors. Today, there are a number of forms of context that are traditionally gathered when creating errors. These members are gathered so that a final error reporting type or function can access them and render them independently of the -`Display` implementation for that specific error type to allow for consistently -formatted and flexible error reports. Today, there are 2 such forms of context -that are traditionally gathered, `backtrace` and `source`. +`Display` implementation for each specific error type. This allows for +consistently formatted and flexible error reports. Today, there are 2 such +forms of context that are traditionally gathered, `backtrace` and `source`. However, the current approach of promoting each form of context to a fn on the `Error` trait doesn't leave room for forms of context that are not commonly used, or forms of context that are defined outside of the standard library. -By adding a generic form of these functions that works around the issues of -monomorphization on trait objects we can support more forms of context and -forms of context that are experimented with outside of the standard library -such as: +## Example use cases this enables * `SpanTrace` a backtrace like type from the `tracing-error` library * zig-like Error Return Traces by extracting `Location` types from errors @@ -40,10 +36,13 @@ such as: * error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file +* Help text such as suggestions or warnings attached to an error report -With a generic form of member access available on `Error` trait objects we -could support a greater diversity of error handling needs and make room for -experimentation on new forms of context in error reports. + +By adding a generic form of these functions that works around the restriction +on generics in vtables we could support a greater diversity of error handling +needs and make room for experimentation with new forms of context in error +reports. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -233,6 +232,12 @@ We could not do this, and continue to add accessor functions to the `Error` trait whenever a new type reaches critical levels of popularity in error reporting. +If we choose to do nothing we will continue to see hacks around the current +limitations on the error trait such as the `Fail` trait, which added the +missing function access methods that didn't previously exist on the `Error` +trait and type erasure / unnecessary boxing of errors to enable downcasting to +extract members. +[1](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). ## Use an alternative to Any for passing generic types across the trait boundary From 6d9a21254a21b2d5a88875c8d33dfcb6f9365aa3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 1 Apr 2020 17:33:06 -0700 Subject: [PATCH 03/40] more edits --- text/0000-dyn-error-generic-member-access.md | 63 +++++++++++--------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index e21f4c9c2d4..a981622fb94 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -30,15 +30,15 @@ used, or forms of context that are defined outside of the standard library. ## Example use cases this enables -* `SpanTrace` a backtrace like type from the `tracing-error` library +* using `backtrace::Backtrace` instead of `std::backtrace::Backtrace` * zig-like Error Return Traces by extracting `Location` types from errors - gathered via `#[track_caller]` + gathered via `#[track_caller]` or some similar mechanism. * error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file +* `SpanTrace` a backtrace like type from the `tracing-error` library * Help text such as suggestions or warnings attached to an error report - By adding a generic form of these functions that works around the restriction on generics in vtables we could support a greater diversity of error handling needs and make room for experimentation with new forms of context in error @@ -47,28 +47,26 @@ reports. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -When implementing error handling in rust there are two main aspects that should -be considered, the creation of errors and the reporting of errors. - Error handling in rust consists mainly of two steps, creation/propogation and -reporting. The `std::error::Error` trait exists to bridge this gap. It does so -by acting as a consistent interface that all Error types can implement to allow -Error Reporting types to handle them in a consistent manner when constructing -reports for end users. +reporting. The `std::error::Error` trait exists to bridge the gap between these +two concerns. It does so by acting as a consistent interface that all error +types can implement to allow error reporting types to handle them in a +consistent manner when constructing reports for end users. The error trait accomplishes this by providing a set of methods for accessing -members of `dyn Error` trait objects. For accessing the message that should be -rendered to the end user the Error trait implements the `Display` trait. For -accessing `dyn Error` members it provides the `source` function, which -conventionally represents the lower level error that caused a subsequent error. -For accessing a `Backtrace` of the state of the stack when an error was created -it provides the `backtrace` function. For all other forms of context relevant -to an Error Report the error trait provides the `context`/`context_any` -functions. - -As an example lets explore how one could implement an error reporting type that -retrieves the Location where each error in the chain was created, if it exists, -and renders it as part of the chain of errors. +members of `dyn Error` trait objects. The main member, the error message +itself, is handled by the `Display` trait which is a requirement for +implementing the Error trait. For accessing `dyn Error` members it provides the +`source` function, which conventionally represents the lower level error that +caused the current error. And for accessing a `Backtrace` of the state of the +stack when an error was created it provides the `backtrace` function. For all +other forms of context relevant to an Error Report the error trait provides the +`context`/`context_any` functions. + +As an example of how to use these types to construct an error report lets +explore how one could implement an error reporting type that retrieves the +Location where each error in the chain was created, if it exists, and renders +it as part of the chain of errors. The goal is to implement an Error Report that looks something like this: @@ -170,11 +168,10 @@ impl fmt::Debug for ErrorReporter { There are two additions necessary to the standard library to implement this proposal: - -First we need to add a function for dyn Error trait objects that will be used -by error reporters to access members given a generic type. This function -circumvents restrictions on generics in trait functions by being implemented -for trait objects only, rather than as a member of the trait itself. +1.) Add a function for dyn Error trait objects that will be used by error +reporters to access members given a generic type. This function circumvents +restrictions on generics in trait functions by being implemented for trait +objects only, rather than as a member of the trait itself. ```rust impl dyn Error { @@ -184,6 +181,18 @@ impl dyn Error { } ``` +With the expected usage: + +```rust +// With explicit parameter passing +let spantrace = error.context::(); + +// With a type inference +fn get_spantrace(error: &(dyn Error + 'static)) -> Option<&SpanTrace> { + error.context() +} +``` + Second we need to add a member to the `Error` trait to provide the `&dyn Any` trait objects to the `context` fn for each member based on the type_id. From c1fd1f6c75b306513fc3858486a0a987f9c36953 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 1 Apr 2020 17:46:12 -0700 Subject: [PATCH 04/40] more edits --- text/0000-dyn-error-generic-member-access.md | 49 +++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index a981622fb94..f13b28d01c2 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -49,9 +49,9 @@ reports. Error handling in rust consists mainly of two steps, creation/propogation and reporting. The `std::error::Error` trait exists to bridge the gap between these -two concerns. It does so by acting as a consistent interface that all error -types can implement to allow error reporting types to handle them in a -consistent manner when constructing reports for end users. +two steps. It does so by acting as a consistent interface that all error types +can implement to allow error reporting types to handle them in a consistent +manner when constructing reports for end users. The error trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. The main member, the error message @@ -61,14 +61,13 @@ implementing the Error trait. For accessing `dyn Error` members it provides the caused the current error. And for accessing a `Backtrace` of the state of the stack when an error was created it provides the `backtrace` function. For all other forms of context relevant to an Error Report the error trait provides the -`context`/`context_any` functions. +`context`/`provide_context` functions. As an example of how to use these types to construct an error report lets explore how one could implement an error reporting type that retrieves the Location where each error in the chain was created, if it exists, and renders -it as part of the chain of errors. - -The goal is to implement an Error Report that looks something like this: +it as part of the chain of errors. Our end goal is to get an error report that +looks something like this: ``` Error: @@ -125,7 +124,7 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn context_any(&self, type_id: TypeID) -> Option<&dyn Any> { + fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { if id == TypeId::of::() { Some(&self.location) } else { @@ -168,7 +167,7 @@ impl fmt::Debug for ErrorReporter { There are two additions necessary to the standard library to implement this proposal: -1.) Add a function for dyn Error trait objects that will be used by error +Add a function for dyn Error trait objects that will be used by error reporters to access members given a generic type. This function circumvents restrictions on generics in trait functions by being implemented for trait objects only, rather than as a member of the trait itself. @@ -176,7 +175,7 @@ objects only, rather than as a member of the trait itself. ```rust impl dyn Error { pub fn context(&self) -> Option<&T> { - self.context_any(TypeId::of::())?.downcast_ref::() + self.provide_context(TypeId::of::())?.downcast_ref::() } } ``` @@ -193,14 +192,26 @@ fn get_spantrace(error: &(dyn Error + 'static)) -> Option<&SpanTrace> { } ``` -Second we need to add a member to the `Error` trait to provide the `&dyn Any` -trait objects to the `context` fn for each member based on the type_id. +Add a member to the `Error` trait to provide the `&dyn Any` trait objects to +the `context` fn for each member based on the type_id. ```rust trait Error { /// ... - fn context_any(&self, id: TypeId) -> Option<&dyn Any> { + fn provide_context(&self, id: TypeId) -> Option<&dyn Any> { + None + } +} +``` + +With the expected usage: + +```rust +fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { + if id == TypeId::of::() { + Some(&self.location) + } else { None } } @@ -229,6 +240,10 @@ trait Error { * This approach cannot return slices or trait objects because of restrictions on `Any` * The alternative solution avoids this issue +* The `context` function name is currently widely used throughout the rust + error handling ecosystem in libraries like `anyhow` and `snafu` as an + ergonomic version of `map_err`. If we settle on `context` as the final name + it will possibly break existing libraries. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -246,7 +261,7 @@ limitations on the error trait such as the `Fail` trait, which added the missing function access methods that didn't previously exist on the `Error` trait and type erasure / unnecessary boxing of errors to enable downcasting to extract members. -[1](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). +[[1]](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). ## Use an alternative to Any for passing generic types across the trait boundary @@ -256,10 +271,10 @@ suggestion is necessarily better, but it is much simpler. * https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b * https://github.com/mystor/object-provider -With this design an implementation of the `context_any` fn might instead look like: +With this design an implementation of the `provide_context` fn might instead look like: ```rust -fn provide<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { +fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { request .provide::(&self.path)? .provide::(&self.path)? @@ -295,7 +310,7 @@ trait. [unresolved-questions]: #unresolved-questions - What should the names of these functions be? - - `context`/`context_ref`/`context_any` + - `context`/`context_ref`/`provide_context` - `member`/`member_ref` - `provide`/`request` - Should we go with the implementation that uses `Any` or the one that supports From f344bd9d6a07b88fc51167f5a3028c52201964a5 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 1 Apr 2020 17:51:21 -0700 Subject: [PATCH 05/40] maybe time to start showing ppl this --- text/0000-dyn-error-generic-member-access.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index f13b28d01c2..97d4321bda7 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -236,10 +236,9 @@ fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { use to check against the type_id * The downcast could be changed to panic when it fails * There is an alternative implementation that mostly avoids this issue -* Introduces more overhead from the downcasts * This approach cannot return slices or trait objects because of restrictions on `Any` - * The alternative solution avoids this issue + * The alternative implementation avoids this issue * The `context` function name is currently widely used throughout the rust error handling ecosystem in libraries like `anyhow` and `snafu` as an ergonomic version of `map_err`. If we settle on `context` as the final name @@ -268,8 +267,9 @@ extract members. Nika Layzell has proposed an alternative implementation using a `Provider` type which avoids using `&dyn Any`. I do not necessarily think that the main suggestion is necessarily better, but it is much simpler. - * https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b - * https://github.com/mystor/object-provider + +* https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b +* https://github.com/mystor/object-provider With this design an implementation of the `provide_context` fn might instead look like: @@ -287,7 +287,9 @@ The advantages of this design are that: 1. It supports accessing trait objects and slices 2. If the user specifies the type they are trying to pass in explicitly they will get compiler errors when the type doesn't match. -3. Less verbose implementation +3. Takes advantage of deref sugar to help with conversions from wrapper types + to inner types. +4. Less verbose implementation The disadvatages are: From 9726cfe18e3150a9da68869bb6e8c87a0ccd7075 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 2 Apr 2020 12:40:34 -0700 Subject: [PATCH 06/40] simplify summary --- text/0000-dyn-error-generic-member-access.md | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 97d4321bda7..d160597e44c 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -7,12 +7,27 @@ [summary]: #summary This RFC proposes a pair of additions to the `Error` trait to support accessing -generic forms of context from `dyn Error` trait objects, one method on the -`Error` trait itself for returning references to members based on a given type -id, and another fn implemented for `dyn Error` that uses a generic return type -to get the type id to pass into the trait object's fn. These functions will act -as a generalized version of `backtrace` and `source`, and would primarily be -used during error reporting when rendering a chain of opaque errors. +generic forms of context from `dyn Error` trait objects. These functions will +act as a generalized version of `backtrace` and `source`, and would primarily +be used during error reporting when rendering a chain of opaque errors. + +```rust +pub trait Error { + /// Provide an untyped reference to a member whose type matches the provided `TypeId`. + /// + /// Returns `None` by default, implementors are encouraged to override. + fn provide_context(&self, ty: TypeId) -> Option<&dyn Any> { + None + } +} + +impl dyn Error { + /// Retrieve a reference to `T`-typed context from the error if it is available. + pub fn context(&self) -> Option<&T> { + self.provide_context(TypeId::of::())?.downcast_ref::() + } +} +``` # Motivation [motivation]: #motivation From 9dd4113588d96ebc556d578bacb47763c8fb87e3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 2 Apr 2020 12:42:59 -0700 Subject: [PATCH 07/40] oops, didnt realize this was a suggested edit --- text/0000-dyn-error-generic-member-access.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d160597e44c..3f4b970b908 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -6,10 +6,16 @@ # Summary [summary]: #summary -This RFC proposes a pair of additions to the `Error` trait to support accessing -generic forms of context from `dyn Error` trait objects. These functions will -act as a generalized version of `backtrace` and `source`, and would primarily -be used during error reporting when rendering a chain of opaque errors. +This RFC proposes two additions to the `Error` trait to support accessing +generic forms of context from `dyn Error` trait objects, generalizing the +pattern used in `backtrace` and `source` and allowing ecosystem iteration on +error reporting infrastructure outside of the standard library. The two +proposed additions are a new trait method `Error::provide_context` which offers +`TypeId`-based member lookup and a new inherent fn `::context` which +makes use of an implementor's `provide_context` to return a typed reference +directly. These additions would primarily be useful in "error reporting" +contexts where we typically no longer have type information and may be +composing errors from many sources. ```rust pub trait Error { From 49fc1d003b330036f9fecac280bb62edd89448b6 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 2 Apr 2020 14:37:02 -0700 Subject: [PATCH 08/40] post eliza review --- text/0000-dyn-error-generic-member-access.md | 166 +++++-------------- 1 file changed, 43 insertions(+), 123 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 3f4b970b908..83d0a8c64bd 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -6,16 +6,7 @@ # Summary [summary]: #summary -This RFC proposes two additions to the `Error` trait to support accessing -generic forms of context from `dyn Error` trait objects, generalizing the -pattern used in `backtrace` and `source` and allowing ecosystem iteration on -error reporting infrastructure outside of the standard library. The two -proposed additions are a new trait method `Error::provide_context` which offers -`TypeId`-based member lookup and a new inherent fn `::context` which -makes use of an implementor's `provide_context` to return a typed reference -directly. These additions would primarily be useful in "error reporting" -contexts where we typically no longer have type information and may be -composing errors from many sources. +This RFC proposes two additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source` and allows ecosystem iteration on error reporting infrastructure outside of the standard library. The two proposed additions are a new trait method `Error::provide_context`, which offers `TypeId`-based member lookup, and a new inherent fn `::context`, which makes use of an implementor's `provide_context` to return a typed reference directly. These additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. ```rust pub trait Error { @@ -38,57 +29,27 @@ impl dyn Error { # Motivation [motivation]: #motivation -Today, there are a number of forms of context that are traditionally gathered -when creating errors. These members are gathered so that a final error -reporting type or function can access them and render them independently of the -`Display` implementation for each specific error type. This allows for -consistently formatted and flexible error reports. Today, there are 2 such -forms of context that are traditionally gathered, `backtrace` and `source`. +In Rust today, errors traditionally gather two forms of context when they are created: context for the *current error message* and context for the *final* *error report*. The `Error` trait exists to provide a consistent interface to context intended for error reports. This context includes the error message, the source error, and, more recently, backtraces. -However, the current approach of promoting each form of context to a fn on the -`Error` trait doesn't leave room for forms of context that are not commonly -used, or forms of context that are defined outside of the standard library. +However, the current approach of promoting each form of context to a method on the `Error` trait doesn't leave room for forms of context that are not commonly used, or forms of context that are defined outside of the standard library. ## Example use cases this enables - * using `backtrace::Backtrace` instead of `std::backtrace::Backtrace` -* zig-like Error Return Traces by extracting `Location` types from errors - gathered via `#[track_caller]` or some similar mechanism. -* error source trees instead of chains by accessing the source of an error as a - slice of errors rather than as a single error, such as a set of errors caused - when parsing a file -* `SpanTrace` a backtrace like type from the `tracing-error` library +* zig-like Error Return Traces by extracting `Location` types from errors gathered via `#[track_caller]` or some similar mechanism. +* error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file +* [`SpanTrace`], a backtrace-like type from the `tracing-error` library * Help text such as suggestions or warnings attached to an error report -By adding a generic form of these functions that works around the restriction -on generics in vtables we could support a greater diversity of error handling -needs and make room for experimentation with new forms of context in error -reports. +By adding a generic form of these functions that works around the restriction on generics in trait objects, we could support a greater diversity of error handling needs, as well as making room for experimentation with new forms of context in error reports. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Error handling in rust consists mainly of two steps, creation/propogation and -reporting. The `std::error::Error` trait exists to bridge the gap between these -two steps. It does so by acting as a consistent interface that all error types -can implement to allow error reporting types to handle them in a consistent -manner when constructing reports for end users. - -The error trait accomplishes this by providing a set of methods for accessing -members of `dyn Error` trait objects. The main member, the error message -itself, is handled by the `Display` trait which is a requirement for -implementing the Error trait. For accessing `dyn Error` members it provides the -`source` function, which conventionally represents the lower level error that -caused the current error. And for accessing a `Backtrace` of the state of the -stack when an error was created it provides the `backtrace` function. For all -other forms of context relevant to an Error Report the error trait provides the -`context`/`provide_context` functions. - -As an example of how to use these types to construct an error report lets -explore how one could implement an error reporting type that retrieves the -Location where each error in the chain was created, if it exists, and renders -it as part of the chain of errors. Our end goal is to get an error report that -looks something like this: +Error handling in Rust consists of two steps: creation/propagation and reporting. The `std::error::Error` trait exists to bridge the gap between these two steps. It does so by acting as a consistent interface that all error types can implement. This allows error reporting types to handle them in a consistent manner when constructing reports for end users. + +The error trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. The main member, the error message itself, is handled by the `Display` trait, which is a requirement for implementing the Error trait. For accessing `dyn Error` members, it provides the `source` function, which conventionally represents the lower level error that caused the current error. And, for accessing a `Backtrace` of the state of the stack when an error was created, it provides the `backtrace` function. For all other forms of context relevant to an error report, the error trait provides the `context` and `provide_context` functions. + +As an example of how to use these types to construct an error report, let’s explore how one could implement an error reporting type. In this example, our error reporting type will retrieve the source code location where each error in the chain was created (if it exists) and render it as part of the chain of errors. Our end goal is to get an error report that looks something like this: ``` Error: @@ -97,8 +58,7 @@ Error: 1: No such file or directory (os error 2) ``` -The first step is to define or use a Location type. In this example we will -define our own but we could use also use `std::panic::Location` for example. +The first step is to define or use a type to represent a source location. In this example, we will define our own, but we could also use `std::panic::Location` or a similar type. ```rust struct Location { @@ -107,7 +67,7 @@ struct Location { } ``` -Next we need to gather the location when creating our error types. +Next, we need to gather the location when creating our error types. ```rust struct ExampleError { @@ -136,8 +96,7 @@ fn read_instrs(path: &Path) -> Result { } ``` -Next we need to implement the `Error` trait to expose these members to the -Error Reporter. +Next, we need to implement the `Error` trait to expose these members to the error reporter. ```rust impl std::error::Error for ExampleError { @@ -155,8 +114,7 @@ impl std::error::Error for ExampleError { } ``` -And finally, we create an error reporter that prints the error and its source -recursively along with the location data if it was gathered. +And, finally, we create an error reporter that prints the error and its source recursively, along with any location data that was gathered ```rust struct ErrorReporter(Box); @@ -185,13 +143,9 @@ impl fmt::Debug for ErrorReporter { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -There are two additions necessary to the standard library to implement this -proposal: +There are two additions necessary to the standard library to implement this proposal: -Add a function for dyn Error trait objects that will be used by error -reporters to access members given a generic type. This function circumvents -restrictions on generics in trait functions by being implemented for trait -objects only, rather than as a member of the trait itself. +Add a function for dyn Error trait objects that will be used by error reporters to access members given a generic type. This function circumvents restrictions on generics in trait functions by being implemented for trait objects only, rather than as a member of the trait itself. ```rust impl dyn Error { @@ -213,8 +167,7 @@ fn get_spantrace(error: &(dyn Error + 'static)) -> Option<&SpanTrace> { } ``` -Add a member to the `Error` trait to provide the `&dyn Any` trait objects to -the `context` fn for each member based on the type_id. +Add a member to the `Error` trait to provide the `&dyn Any` trait objects to the `context` fn for each member based on the type_id. ```rust trait Error { @@ -241,29 +194,19 @@ fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { # Drawbacks [drawbacks]: #drawbacks -* The API for defining how to return types is cumbersome and possibly not - accessible for new rust users. - * If the type is stored in an Option getting it converted to an `&Any` will - probably challenge new devs, this can be made easier with documented - examples covering common use cases and macros like `thiserror`. +* The API for defining how to return types is cumbersome and possibly not accessible for new rust users. + * If the type is stored in an Option getting it converted to an `&Any` will probably challenge new devs, this can be made easier with documented examples covering common use cases and macros like `thiserror`. ```rust } else if typeid == TypeId::of::() { self.span_trace.as_ref().map(|s| s as &dyn Any) } ``` -* When you return the wrong type and the downcast fails you get `None` rather - than a compiler error guiding you to the right return type, which can make it - challenging to debug mismatches between the type you return and the type you - use to check against the type_id - * The downcast could be changed to panic when it fails +* When you return the wrong type and the downcast fails you get `None` rather than a compiler error guiding you to the right return type, which can make it challenging to debug mismatches between the type you return and the type you use to check against the type_id The downcast could be changed to panic when it fails * There is an alternative implementation that mostly avoids this issue -* This approach cannot return slices or trait objects because of restrictions - on `Any` +* This approach cannot return slices or trait objects because of restrictions on `Any` * The alternative implementation avoids this issue -* The `context` function name is currently widely used throughout the rust - error handling ecosystem in libraries like `anyhow` and `snafu` as an - ergonomic version of `map_err`. If we settle on `context` as the final name - it will possibly break existing libraries. +* The `context` function name is currently widely used throughout the rust error handling ecosystem in libraries like `anyhow` and `snafu` as an ergonomic version of `map_err`. If we settle on `context` as the final name it will possibly break existing libraries. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -272,22 +215,13 @@ The two alternatives I can think of are: ## Do Nothing -We could not do this, and continue to add accessor functions to the `Error` -trait whenever a new type reaches critical levels of popularity in error -reporting. +We could not do this, and continue to add accessor functions to the `Error` trait whenever a new type reaches critical levels of popularity in error reporting. -If we choose to do nothing we will continue to see hacks around the current -limitations on the error trait such as the `Fail` trait, which added the -missing function access methods that didn't previously exist on the `Error` -trait and type erasure / unnecessary boxing of errors to enable downcasting to -extract members. -[[1]](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). +If we choose to do nothing we will continue to see hacks around the current limitations on the error trait such as the `Fail` trait, which added the missing function access methods that didn't previously exist on the `Error` trait and type erasure / unnecessary boxing of errors to enable downcasting to extract members. [[1]](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). ## Use an alternative to Any for passing generic types across the trait boundary -Nika Layzell has proposed an alternative implementation using a `Provider` type -which avoids using `&dyn Any`. I do not necessarily think that the main -suggestion is necessarily better, but it is much simpler. +Nika Layzell has proposed an alternative implementation using a `Provider` type which avoids using `&dyn Any`. I do not necessarily think that the main suggestion is necessarily better, but it is much simpler. * https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b * https://github.com/mystor/object-provider @@ -306,51 +240,37 @@ fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult< The advantages of this design are that: 1. It supports accessing trait objects and slices -2. If the user specifies the type they are trying to pass in explicitly they - will get compiler errors when the type doesn't match. -3. Takes advantage of deref sugar to help with conversions from wrapper types - to inner types. +2. If the user specifies the type they are trying to pass in explicitly they will get compiler errors when the type doesn't match. +3. Takes advantage of deref sugar to help with conversions from wrapper types to inner types. 4. Less verbose implementation The disadvatages are: 1. More verbose function signature, very lifetime heavy 2. The Request type uses unsafe code which needs to be verified -3. could encourage implementations where they pass the provider to - `source.provide` first which would prevent the error reporter from knowing - which error in the chain gathered each piece of context and might cause - context to show up multiple times in a report. +3. could encourage implementations where they pass the provider to `source.provide` first which would prevent the error reporter from knowing which error in the chain gathered each piece of context and might cause context to show up multiple times in a report. # Prior art [prior-art]: #prior-art -I do not know of any other languages whose error handling has similar -facilities for accessing members when reporting errors. For the most part prior -art exists within rust itself in the form of previous additions to the `Error` -trait. +I do not know of any other languages whose error handling has similar facilities for accessing members when reporting errors. For the most part prior art exists within rust itself in the form of previous additions to the `Error` trait. # Unresolved questions [unresolved-questions]: #unresolved-questions -- What should the names of these functions be? - - `context`/`context_ref`/`provide_context` - - `member`/`member_ref` - - `provide`/`request` -- Should we go with the implementation that uses `Any` or the one that supports - accessing dynamically sized types like traits and slices? -- Should there be a by value version for accessing temporaries? - - I bring this up specifically for the case where you want to use this - function to get an `Option<&[&dyn Error]>` out of an error, in this case - its unlikely that the error behind the trait object is actually storing - the errors as `dyn Errors`, and theres no easy way to allocate storage to - store the trait objects. +* What should the names of these functions be? + * `context`/`context_ref`/`provide_context` + * `member`/`member_ref` + * `provide`/`request` +* Should we go with the implementation that uses `Any` or the one that supports accessing dynamically sized types like traits and slices? +* Should there be a by value version for accessing temporaries? + * I bring this up specifically for the case where you want to use this function to get an `Option<&[&dyn Error]>` out of an error, in this case its unlikely that the error behind the trait object is actually storing the errors as `dyn Errors`, and theres no easy way to allocate storage to store the trait objects. # Future possibilities [future-possibilities]: #future-possibilities -I'd love to see the various error creating libraries like `thiserror` adding -support for making members exportable as context for reporters. +I'd love to see the various error creating libraries like `thiserror` adding support for making members exportable as context for reporters. + +Also, I'm interested in adding support for `Error Return Traces`, similar to zigs, and I think that this accessor function might act as a critical piece of that implementation. -Also, I'm interested in adding support for `Error Return Traces`, similar to -zigs, and I think that this accessor function might act as a critical piece of -that implementation. +[`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html From f2073ace53901b072ebff65cb777889d0fad5b93 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 6 Apr 2020 09:53:22 -0700 Subject: [PATCH 09/40] Adams comments --- text/0000-dyn-error-generic-member-access.md | 48 ++++++++++---------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 83d0a8c64bd..c99ff899fe0 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -6,14 +6,14 @@ # Summary [summary]: #summary -This RFC proposes two additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source` and allows ecosystem iteration on error reporting infrastructure outside of the standard library. The two proposed additions are a new trait method `Error::provide_context`, which offers `TypeId`-based member lookup, and a new inherent fn `::context`, which makes use of an implementor's `provide_context` to return a typed reference directly. These additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. +This RFC proposes two additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source` and allows ecosystem iteration on error reporting infrastructure outside of the standard library. The two proposed additions are a new trait method `Error::get_context`, which offers `TypeId`-based member lookup, and a new inherent fn `::context`, which makes use of an implementor's `get_context` to return a typed reference directly. These additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. ```rust pub trait Error { /// Provide an untyped reference to a member whose type matches the provided `TypeId`. /// /// Returns `None` by default, implementors are encouraged to override. - fn provide_context(&self, ty: TypeId) -> Option<&dyn Any> { + fn get_context(&self, ty: TypeId) -> Option<&dyn Any> { None } } @@ -21,7 +21,7 @@ pub trait Error { impl dyn Error { /// Retrieve a reference to `T`-typed context from the error if it is available. pub fn context(&self) -> Option<&T> { - self.provide_context(TypeId::of::())?.downcast_ref::() + self.get_context(TypeId::of::())?.downcast_ref::() } } ``` @@ -29,25 +29,24 @@ impl dyn Error { # Motivation [motivation]: #motivation -In Rust today, errors traditionally gather two forms of context when they are created: context for the *current error message* and context for the *final* *error report*. The `Error` trait exists to provide a consistent interface to context intended for error reports. This context includes the error message, the source error, and, more recently, backtraces. +In Rust, errors typically gather two forms of context when they are created: context for the *current error message* and context for the *final* *error report*. The `Error` trait exists to provide a interface to context intended for error reports. This context includes the error message, the source error, and, more recently, backtraces. However, the current approach of promoting each form of context to a method on the `Error` trait doesn't leave room for forms of context that are not commonly used, or forms of context that are defined outside of the standard library. ## Example use cases this enables -* using `backtrace::Backtrace` instead of `std::backtrace::Backtrace` -* zig-like Error Return Traces by extracting `Location` types from errors gathered via `#[track_caller]` or some similar mechanism. +* using alternatives to `std::backtrace::Backtrace` such as `backtrace::Backtrace` or [`SpanTrace`] +* zig-like Error Return Traces by extracting `Location` types from errors gathered via `#[track_caller]` or similar. * error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file -* [`SpanTrace`], a backtrace-like type from the `tracing-error` library * Help text such as suggestions or warnings attached to an error report -By adding a generic form of these functions that works around the restriction on generics in trait objects, we could support a greater diversity of error handling needs, as well as making room for experimentation with new forms of context in error reports. +To support these use cases without ecosystem fragmentation, we would extend Error's vtable with a dynamic context API that allows implementors and clients to enrich errors in an opt-in fashion. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Error handling in Rust consists of two steps: creation/propagation and reporting. The `std::error::Error` trait exists to bridge the gap between these two steps. It does so by acting as a consistent interface that all error types can implement. This allows error reporting types to handle them in a consistent manner when constructing reports for end users. +Error handling in Rust consists of two steps: creation/propagation and reporting. The `std::error::Error` trait exists to bridge the gap between these two steps. It does so by acting as a interface that all error types can implement. This allows error reporting types to handle them in a consistent manner when constructing reports for end users. -The error trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. The main member, the error message itself, is handled by the `Display` trait, which is a requirement for implementing the Error trait. For accessing `dyn Error` members, it provides the `source` function, which conventionally represents the lower level error that caused the current error. And, for accessing a `Backtrace` of the state of the stack when an error was created, it provides the `backtrace` function. For all other forms of context relevant to an error report, the error trait provides the `context` and `provide_context` functions. +The error trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires types implement the display trait, which acts as the interface to the main member, the error message itself. It provides the `source` function for accessing `dyn Error` members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error report, the error trait provides the `context` and `get_context` functions. As an example of how to use these types to construct an error report, let’s explore how one could implement an error reporting type. In this example, our error reporting type will retrieve the source code location where each error in the chain was created (if it exists) and render it as part of the chain of errors. Our end goal is to get an error report that looks something like this: @@ -58,7 +57,7 @@ Error: 1: No such file or directory (os error 2) ``` -The first step is to define or use a type to represent a source location. In this example, we will define our own, but we could also use `std::panic::Location` or a similar type. +The first step is to define or use a type to represent a source location. In this example, we will define our own: ```rust struct Location { @@ -104,7 +103,7 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { + fn get_context(&self, type_id: TypeId) -> Option<&dyn Any> { if id == TypeId::of::() { Some(&self.location) } else { @@ -145,12 +144,12 @@ impl fmt::Debug for ErrorReporter { There are two additions necessary to the standard library to implement this proposal: -Add a function for dyn Error trait objects that will be used by error reporters to access members given a generic type. This function circumvents restrictions on generics in trait functions by being implemented for trait objects only, rather than as a member of the trait itself. +Add a function for dyn Error trait objects that will be used by error reporters to access members given a type. This function circumvents restrictions on generics in trait functions by being implemented for trait objects only, rather than as a member of the trait itself. ```rust impl dyn Error { pub fn context(&self) -> Option<&T> { - self.provide_context(TypeId::of::())?.downcast_ref::() + self.get_context(TypeId::of::())?.downcast_ref::() } } ``` @@ -173,7 +172,7 @@ Add a member to the `Error` trait to provide the `&dyn Any` trait objects to the trait Error { /// ... - fn provide_context(&self, id: TypeId) -> Option<&dyn Any> { + fn get_context(&self, id: TypeId) -> Option<&dyn Any> { None } } @@ -182,7 +181,7 @@ trait Error { With the expected usage: ```rust -fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { +fn get_context(&self, type_id: TypeId) -> Option<&dyn Any> { if id == TypeId::of::() { Some(&self.location) } else { @@ -201,7 +200,8 @@ fn provide_context(&self, type_id: TypeId) -> Option<&dyn Any> { self.span_trace.as_ref().map(|s| s as &dyn Any) } ``` -* When you return the wrong type and the downcast fails you get `None` rather than a compiler error guiding you to the right return type, which can make it challenging to debug mismatches between the type you return and the type you use to check against the type_id The downcast could be changed to panic when it fails +# TODO rewrite +* When you return the wrong type and the downcast fails you get `None` rather than a compiler error guiding you to the right return type, which can make it challenging to debug mismatches between the type you return and the type you use to check against the type_id * There is an alternative implementation that mostly avoids this issue * This approach cannot return slices or trait objects because of restrictions on `Any` * The alternative implementation avoids this issue @@ -226,10 +226,10 @@ Nika Layzell has proposed an alternative implementation using a `Provider` type * https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b * https://github.com/mystor/object-provider -With this design an implementation of the `provide_context` fn might instead look like: +With this design an implementation of the `get_context` fn might instead look like: ```rust -fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { +fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { request .provide::(&self.path)? .provide::(&self.path)? @@ -259,18 +259,20 @@ I do not know of any other languages whose error handling has similar facilities [unresolved-questions]: #unresolved-questions * What should the names of these functions be? - * `context`/`context_ref`/`provide_context` + * `context`/`context_ref`/`get_context`/`provide_context` * `member`/`member_ref` * `provide`/`request` * Should we go with the implementation that uses `Any` or the one that supports accessing dynamically sized types like traits and slices? * Should there be a by value version for accessing temporaries? - * I bring this up specifically for the case where you want to use this function to get an `Option<&[&dyn Error]>` out of an error, in this case its unlikely that the error behind the trait object is actually storing the errors as `dyn Errors`, and theres no easy way to allocate storage to store the trait objects. + * We bring this up specifically for the case where you want to use this function to get an `Option<&[&dyn Error]>` out of an error, in this case its unlikely that the error behind the trait object is actually storing the errors as `dyn Errors`, and theres no easy way to allocate storage to store the trait objects. +* How should context handle failed downcasts? + * suggestion: panic, as providing a type that doesn't match the typeid requested is a program error # Future possibilities [future-possibilities]: #future-possibilities -I'd love to see the various error creating libraries like `thiserror` adding support for making members exportable as context for reporters. +We'd love to see the various error creating libraries like `thiserror` adding support for making members exportable as context for reporters. -Also, I'm interested in adding support for `Error Return Traces`, similar to zigs, and I think that this accessor function might act as a critical piece of that implementation. +Also, we're interested in adding support for `Error Return Traces`, similar to zigs, and I think that this accessor function might act as a critical piece of that implementation. [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html From 1fc15e92b3f7d5a57f49904272dd5b3817647669 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 15:21:11 -0700 Subject: [PATCH 10/40] rewrite to focus on nika's version --- text/0000-dyn-error-generic-member-access.md | 383 ++++++++++++++----- 1 file changed, 282 insertions(+), 101 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index c99ff899fe0..8d52f7a9447 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -6,49 +6,122 @@ # Summary [summary]: #summary -This RFC proposes two additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source` and allows ecosystem iteration on error reporting infrastructure outside of the standard library. The two proposed additions are a new trait method `Error::get_context`, which offers `TypeId`-based member lookup, and a new inherent fn `::context`, which makes use of an implementor's `get_context` to return a typed reference directly. These additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. +This RFC proposes additions to the `Error` trait to support accessing generic +forms of context from `dyn Error` trait objects. This generalizes the pattern +used in `backtrace` and `source` and allows ecosystem iteration on error +reporting infrastructure outside of the standard library. The two proposed +additions are a new trait method `Error::get_context`, which offers +`TypeId`-based member lookup, and a new inherent fn `::context`, +which makes use of an implementor's `get_context` to return a typed reference +directly. These additions would primarily be useful in "error reporting" +contexts, where we typically no longer have type information and may be +composing errors from many sources. + +The names here are just placeholders. The specifics of the `Request` type are a +suggested starting point. And the `Request` style api could be replaced with a +much simpler API based on `TypeId` + `dyn Any` at the cost of being +incompatible with dynamically sized types. The basic proposal is this: + +Add this method to the `Error` trait ```rust pub trait Error { - /// Provide an untyped reference to a member whose type matches the provided `TypeId`. - /// - /// Returns `None` by default, implementors are encouraged to override. - fn get_context(&self, ty: TypeId) -> Option<&dyn Any> { - None + // ... + + /// Provides an object of type `T` in response to this request. + fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + Ok(request) } } +``` -impl dyn Error { - /// Retrieve a reference to `T`-typed context from the error if it is available. - pub fn context(&self) -> Option<&T> { - self.get_context(TypeId::of::())?.downcast_ref::() - } +Where an example implementation of this method would look like: + +```rust +fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + request + .provide::(&self.backtrace)? + .provide::(&self.span_trace)? + .provide::(&self.source)? + .provide::>>(&self.locations)? + .provide::<[&'static Location<'static>>(&self.locations) +} +``` + +And usage would then look like this: + +```rust +let e: &dyn Error = &concrete_error; + +if let Some(bt) = e.context::() { + println!("{}", bt); } ``` # Motivation [motivation]: #motivation -In Rust, errors typically gather two forms of context when they are created: context for the *current error message* and context for the *final* *error report*. The `Error` trait exists to provide a interface to context intended for error reports. This context includes the error message, the source error, and, more recently, backtraces. +In Rust, errors typically gather two forms of context when they are created: +context for the *current error message* and context for the *final* *error +report*. The `Error` trait exists to provide a interface to context intended +for error reports. This context includes the error message, the source error, +and, more recently, backtraces. -However, the current approach of promoting each form of context to a method on the `Error` trait doesn't leave room for forms of context that are not commonly used, or forms of context that are defined outside of the standard library. +However, the current approach of promoting each form of context to a method on +the `Error` trait doesn't leave room for forms of context that are not commonly +used, or forms of context that are defined outside of the standard library. +Adding a generic equivalent to these member access functions would leave room +for many more forms of context in error reports. ## Example use cases this enables -* using alternatives to `std::backtrace::Backtrace` such as `backtrace::Backtrace` or [`SpanTrace`] -* zig-like Error Return Traces by extracting `Location` types from errors gathered via `#[track_caller]` or similar. -* error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file + +* using alternatives to `std::backtrace::Backtrace` such as + `backtrace::Backtrace` or [`SpanTrace`] +* zig-like Error Return Traces by extracting `Location` types from errors + gathered via `#[track_caller]` or similar. +* error source trees instead of chains by accessing the source of an error as a + slice of errors rather than as a single error, such as a set of errors caused + when parsing a file * Help text such as suggestions or warnings attached to an error report -To support these use cases without ecosystem fragmentation, we would extend Error's vtable with a dynamic context API that allows implementors and clients to enrich errors in an opt-in fashion. +To support these use cases without ecosystem fragmentation, we would extend the +Error trait with a dynamic context API that allows implementors and clients to +enrich errors in an opt-in fashion. -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation +## Moving `Error` into `libcore` -Error handling in Rust consists of two steps: creation/propagation and reporting. The `std::error::Error` trait exists to bridge the gap between these two steps. It does so by acting as a interface that all error types can implement. This allows error reporting types to handle them in a consistent manner when constructing reports for end users. +Adding a generic member access function to the Error trait and removing the +`backtrace` fn would make it possible to move the `Error` trait to libcore +without losing support for backtraces on std. The only difference would be that +in places where you can currently write `error.backtrace()` on nightly you +would instead need to write `error.context::()`. -The error trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires types implement the display trait, which acts as the interface to the main member, the error message itself. It provides the `source` function for accessing `dyn Error` members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error report, the error trait provides the `context` and `get_context` functions. +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation -As an example of how to use these types to construct an error report, let’s explore how one could implement an error reporting type. In this example, our error reporting type will retrieve the source code location where each error in the chain was created (if it exists) and render it as part of the chain of errors. Our end goal is to get an error report that looks something like this: +Error handling in Rust consists of three steps: creation/propagation, handling, +and reporting. The `std::error::Error` trait exists to bridge the gap between +creation and reporting. It does so by acting as a interface that all error +types can implement that defines how to access context intended for error +reports, such as the error message, source, or location it was created. This +allows error reporting types to handle errors in a consistent manner when +constructing reports for end users while still retaining control over the +format of the full report. + +The error trait accomplishes this by providing a set of methods for accessing +members of `dyn Error` trait objects. It requires types implement the display +trait, which acts as the interface to the main member, the error message +itself. It provides the `source` function for accessing `dyn Error` members, +which typically represent the current error's cause. It provides the +`backtrace` function, for accessing a `Backtrace` of the state of the stack +when an error was created. For all other forms of context relevant to an error +report, the error trait provides the `context` and `get_context` functions. + +As an example of how to use this interface to construct an error report, let’s +explore how one could implement an error reporting type. In this example, our +error reporting type will retrieve the source code location where each error in +the chain was created (if it exists) and render it as part of the chain of +errors. Our end goal is to get an error report that looks something like this: ``` Error: @@ -57,7 +130,8 @@ Error: 1: No such file or directory (os error 2) ``` -The first step is to define or use a type to represent a source location. In this example, we will define our own: +The first step is to define or use a type to represent a source location. In +this example, we will define our own: ```rust struct Location { @@ -95,7 +169,7 @@ fn read_instrs(path: &Path) -> Result { } ``` -Next, we need to implement the `Error` trait to expose these members to the error reporter. +Then, we need to implement the `Error` trait to expose these members to the error reporter. ```rust impl std::error::Error for ExampleError { @@ -103,17 +177,14 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn get_context(&self, type_id: TypeId) -> Option<&dyn Any> { - if id == TypeId::of::() { - Some(&self.location) - } else { - None - } + fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + request.provide::(&self.location) } } ``` -And, finally, we create an error reporter that prints the error and its source recursively, along with any location data that was gathered +And, finally, we create an error reporter that prints the error and its source +recursively, along with any location data that was gathered ```rust struct ErrorReporter(Box); @@ -142,50 +213,146 @@ impl fmt::Debug for ErrorReporter { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -There are two additions necessary to the standard library to implement this proposal: +The following changes need to be made to implement this proposal: + +## Add a type like [`ObjectProvider::Request`] to std -Add a function for dyn Error trait objects that will be used by error reporters to access members given a type. This function circumvents restrictions on generics in trait functions by being implemented for trait objects only, rather than as a member of the trait itself. +This type fills the same role as `&dyn Any` except that it supports other trait +objects as the requested type. + +Here is the implementation for the proof of concept: ```rust -impl dyn Error { - pub fn context(&self) -> Option<&T> { - self.get_context(TypeId::of::())?.downcast_ref::() - } +/// A dynamic request for an object based on its type. +/// +/// `'r` is the lifetime of request, and `'out` is the lifetime of the requested +/// reference. +pub struct Request<'r, 'out> { + buf: NonNull, + _marker: PhantomData<&'r mut &'out Cell<()>>, } -``` -With the expected usage: +impl<'r, 'out> Request<'r, 'out> { + /// Provides an object of type `T` in response to this request. + /// + /// Returns `Err(FulfilledRequest)` if the value was successfully provided, + /// and `Ok(self)` if `T` was not the type being requested. + /// + /// This method can be chained within `provide` implementations using the + /// `?` operator to concisely provide multiple objects. + pub fn provide(self, value: &'out T) -> ProvideResult<'r, 'out> { + self.provide_with(|| value) + } -```rust -// With explicit parameter passing -let spantrace = error.context::(); + /// Lazily provides an object of type `T` in response to this request. + /// + /// Returns `Err(FulfilledRequest)` if the value was successfully provided, + /// and `Ok(self)` if `T` was not the type being requested. + /// + /// The passed closure is only called if the value will be successfully + /// provided. + /// + /// This method can be chained within `provide` implementations using the + /// `?` operator to concisely provide multiple objects. + pub fn provide_with(mut self, cb: F) -> ProvideResult<'r, 'out> + where + F: FnOnce() -> &'out T, + { + match self.downcast_buf::() { + Some(this) => { + debug_assert!( + this.value.is_none(), + "Multiple requests to a `RequestBuf` were acquired?" + ); + this.value = Some(cb()); + Err(FulfilledRequest(PhantomData)) + } + None => Ok(self), + } + } + + /// Get the `TypeId` of the requested type. + pub fn type_id(&self) -> TypeId { + unsafe { *self.buf.as_ref() } + } + + /// Returns `true` if the requested type is the same as `T` + pub fn is(&self) -> bool { + self.type_id() == TypeId::of::() + } + + /// Try to downcast this `Request` into a reference to the typed + /// `RequestBuf` object. + /// + /// This method will return `None` if `self` was not derived from a + /// `RequestBuf<'_, T>`. + fn downcast_buf(&mut self) -> Option<&mut RequestBuf<'out, T>> { + if self.is::() { + unsafe { Some(&mut *(self.buf.as_ptr() as *mut RequestBuf<'out, T>)) } + } else { + None + } + } -// With a type inference -fn get_spantrace(error: &(dyn Error + 'static)) -> Option<&SpanTrace> { - error.context() + /// Calls the provided closure with a request for the the type `T`, returning + /// `Some(&T)` if the request was fulfilled, and `None` otherwise. + /// + /// The `ObjectProviderExt` trait provides helper methods specifically for + /// types implementing `ObjectProvider`. + pub fn with(f: F) -> Option<&'out T> + where + for<'a> F: FnOnce(Request<'a, 'out>) -> ProvideResult<'a, 'out>, + { + let mut buf = RequestBuf { + type_id: TypeId::of::(), + value: None, + }; + let _ = f(Request { + buf: unsafe { + NonNull::new_unchecked(&mut buf as *mut RequestBuf<'out, T> as *mut TypeId) + }, + _marker: PhantomData, + }); + buf.value + } } + +// Needs to have a known layout so we can do unsafe pointer shenanigans. +#[repr(C)] +struct RequestBuf<'a, T: ?Sized> { + type_id: TypeId, + value: Option<&'a T>, +} + +/// Marker type indicating a request has been fulfilled. +pub struct FulfilledRequest(PhantomData<&'static Cell<()>>); + +/// Provider method return type. +/// +/// Either `Ok(Request)` for an unfulfilled request, or `Err(FulfilledRequest)` +/// if the request was fulfilled. +pub type ProvideResult<'r, 'a> = Result, FulfilledRequest>; ``` -Add a member to the `Error` trait to provide the `&dyn Any` trait objects to the `context` fn for each member based on the type_id. +## Define a generic accessor on the `Error` trait ```rust -trait Error { - /// ... +pub trait Error { + // ... - fn get_context(&self, id: TypeId) -> Option<&dyn Any> { - None + /// Provides an object of type `T` in response to this request. + fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + Ok(request) } } ``` -With the expected usage: +## Use this `Request` type to handle passing generic types out of the trait object ```rust -fn get_context(&self, type_id: TypeId) -> Option<&dyn Any> { - if id == TypeId::of::() { - Some(&self.location) - } else { - None +impl dyn Error { + pub fn context(&self) -> Option<&T> { + Request::with::(|req| self.get_context(req)) } } ``` @@ -193,19 +360,13 @@ fn get_context(&self, type_id: TypeId) -> Option<&dyn Any> { # Drawbacks [drawbacks]: #drawbacks -* The API for defining how to return types is cumbersome and possibly not accessible for new rust users. - * If the type is stored in an Option getting it converted to an `&Any` will probably challenge new devs, this can be made easier with documented examples covering common use cases and macros like `thiserror`. -```rust -} else if typeid == TypeId::of::() { - self.span_trace.as_ref().map(|s| s as &dyn Any) -} -``` -# TODO rewrite -* When you return the wrong type and the downcast fails you get `None` rather than a compiler error guiding you to the right return type, which can make it challenging to debug mismatches between the type you return and the type you use to check against the type_id - * There is an alternative implementation that mostly avoids this issue -* This approach cannot return slices or trait objects because of restrictions on `Any` - * The alternative implementation avoids this issue -* The `context` function name is currently widely used throughout the rust error handling ecosystem in libraries like `anyhow` and `snafu` as an ergonomic version of `map_err`. If we settle on `context` as the final name it will possibly break existing libraries. +* The `Request` api is being added purely to help with this fn, there may be + some design iteration here that could be done to make this more generally + applicable, it seems very similar to `dyn Any`. +* The `context` function name is currently widely used throughout the rust + error handling ecosystem in libraries like `anyhow` and `snafu` as an + ergonomic version of `map_err`. If we settle on `context` as the final name + it will possibly break existing libraries. # Rationale and alternatives @@ -215,45 +376,50 @@ The two alternatives I can think of are: ## Do Nothing -We could not do this, and continue to add accessor functions to the `Error` trait whenever a new type reaches critical levels of popularity in error reporting. +We could not do this, and continue to add accessor functions to the `Error` +trait whenever a new type reaches critical levels of popularity in error +reporting. -If we choose to do nothing we will continue to see hacks around the current limitations on the error trait such as the `Fail` trait, which added the missing function access methods that didn't previously exist on the `Error` trait and type erasure / unnecessary boxing of errors to enable downcasting to extract members. [[1]](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). +If we choose to do nothing we will continue to see hacks around the current +limitations on the error trait such as the `Fail` trait, which added the +missing function access methods that didn't previously exist on the `Error` +trait and type erasure / unnecessary boxing of errors to enable downcasting to +extract members. +[[1]](https://docs.rs/tracing-error/0.1.2/src/tracing_error/error.rs.html#269-274). -## Use an alternative to Any for passing generic types across the trait boundary -Nika Layzell has proposed an alternative implementation using a `Provider` type which avoids using `&dyn Any`. I do not necessarily think that the main suggestion is necessarily better, but it is much simpler. +## Use an alternative proposal that relies on the `Any` trait for downcasting -* https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0af9dbf0cd20fa0bea6cff16a419916b -* https://github.com/mystor/object-provider - -With this design an implementation of the `get_context` fn might instead look like: +This approach is much simpler, but critically doesn't support providing +dynamically sized types, and it is more error prone because it cannot provide +compile time errors when the type you provide does not match the type_id you +were given. ```rust -fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { - request - .provide::(&self.path)? - .provide::(&self.path)? - .provide::(&self.path) +pub trait Error { + /// Provide an untyped reference to a member whose type matches the provided `TypeId`. + /// + /// Returns `None` by default, implementors are encouraged to override. + fn provide(&self, ty: TypeId) -> Option<&dyn Any> { + None + } } -``` - -The advantages of this design are that: -1. It supports accessing trait objects and slices -2. If the user specifies the type they are trying to pass in explicitly they will get compiler errors when the type doesn't match. -3. Takes advantage of deref sugar to help with conversions from wrapper types to inner types. -4. Less verbose implementation - -The disadvatages are: - -1. More verbose function signature, very lifetime heavy -2. The Request type uses unsafe code which needs to be verified -3. could encourage implementations where they pass the provider to `source.provide` first which would prevent the error reporter from knowing which error in the chain gathered each piece of context and might cause context to show up multiple times in a report. +impl dyn Error { + /// Retrieve a reference to `T`-typed context from the error if it is available. + pub fn request(&self) -> Option<&T> { + self.get_context(TypeId::of::())?.downcast_ref::() + } +} +``` # Prior art [prior-art]: #prior-art -I do not know of any other languages whose error handling has similar facilities for accessing members when reporting errors. For the most part prior art exists within rust itself in the form of previous additions to the `Error` trait. +I do not know of any other languages whose error handling has similar +facilities for accessing members when reporting errors. For the most part prior +art exists within rust itself in the form of previous additions to the `Error` +trait. # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -262,17 +428,32 @@ I do not know of any other languages whose error handling has similar facilities * `context`/`context_ref`/`get_context`/`provide_context` * `member`/`member_ref` * `provide`/`request` -* Should we go with the implementation that uses `Any` or the one that supports accessing dynamically sized types like traits and slices? * Should there be a by value version for accessing temporaries? - * We bring this up specifically for the case where you want to use this function to get an `Option<&[&dyn Error]>` out of an error, in this case its unlikely that the error behind the trait object is actually storing the errors as `dyn Errors`, and theres no easy way to allocate storage to store the trait objects. + * We bring this up specifically for the case where you want to use this + function to get an `Option<&[&dyn Error]>` out of an error, in this case + its unlikely that the error behind the trait object is actually storing + the errors as `dyn Errors`, and theres no easy way to allocate storage to + store the trait objects. * How should context handle failed downcasts? - * suggestion: panic, as providing a type that doesn't match the typeid requested is a program error + * suggestion: panic, as providing a type that doesn't match the typeid + requested is a program error # Future possibilities [future-possibilities]: #future-possibilities -We'd love to see the various error creating libraries like `thiserror` adding support for making members exportable as context for reporters. +Libraries like `thiserror` could add support for making members exportable as +context for reporters. + +This opens the door to supporting `Error Return Traces`, similar to zigs, where +if each return location is stored in a `Vec<&'static Location<'static>>` a full +return trace could be built up with: -Also, we're interested in adding support for `Error Return Traces`, similar to zigs, and I think that this accessor function might act as a critical piece of that implementation. +```rust +let mut locations = e + .chain() + .filter_map(|e| e.context::<[&'static Location<'static>]>()) + .flat_map(|locs| locs.iter()); +``` [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html +[`ObjectProvider::Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs From 460c523395016ff54b7ff4d479b329f0cdcbca04 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 15:42:10 -0700 Subject: [PATCH 11/40] proof reading --- text/0000-dyn-error-generic-member-access.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 8d52f7a9447..5a3bb639ac4 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -9,18 +9,15 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source` and allows ecosystem iteration on error -reporting infrastructure outside of the standard library. The two proposed -additions are a new trait method `Error::get_context`, which offers -`TypeId`-based member lookup, and a new inherent fn `::context`, +reporting infrastructure outside of the standard library. This proposal adds +the method `Error::get_context` to the error trait, which offers `TypeId`-based +member lookup, and a new inherent fn `::context`, which makes use of an implementor's `get_context` to return a typed reference directly. These additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. -The names here are just placeholders. The specifics of the `Request` type are a -suggested starting point. And the `Request` style api could be replaced with a -much simpler API based on `TypeId` + `dyn Any` at the cost of being -incompatible with dynamically sized types. The basic proposal is this: +## TLDR Add this method to the `Error` trait @@ -35,7 +32,7 @@ pub trait Error { } ``` -Where an example implementation of this method would look like: +Example implementation: ```rust fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { @@ -48,7 +45,7 @@ fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, } ``` -And usage would then look like this: +Example usage: ```rust let e: &dyn Error = &concrete_error; @@ -63,7 +60,7 @@ if let Some(bt) = e.context::() { In Rust, errors typically gather two forms of context when they are created: context for the *current error message* and context for the *final* *error -report*. The `Error` trait exists to provide a interface to context intended +report*. The `Error` trait exists to provide an interface to context intended for error reports. This context includes the error message, the source error, and, more recently, backtraces. From c8b80ed77b8001f02bfb4c6f8cd4752273b80842 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 15:45:14 -0700 Subject: [PATCH 12/40] boop --- text/0000-dyn-error-generic-member-access.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 5a3bb639ac4..f53736feef2 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -67,10 +67,14 @@ and, more recently, backtraces. However, the current approach of promoting each form of context to a method on the `Error` trait doesn't leave room for forms of context that are not commonly used, or forms of context that are defined outside of the standard library. -Adding a generic equivalent to these member access functions would leave room -for many more forms of context in error reports. -## Example use cases this enables +## Extracting non-std types from `dyn Errors` + +By adding a generic form of these member access functions we are no longer +restricted to types defined in the standard library. This opens the door to +many new forms of error reporting. + +### Example use cases this enables * using alternatives to `std::backtrace::Backtrace` such as `backtrace::Backtrace` or [`SpanTrace`] @@ -81,10 +85,6 @@ for many more forms of context in error reports. when parsing a file * Help text such as suggestions or warnings attached to an error report -To support these use cases without ecosystem fragmentation, we would extend the -Error trait with a dynamic context API that allows implementors and clients to -enrich errors in an opt-in fashion. - ## Moving `Error` into `libcore` Adding a generic member access function to the Error trait and removing the From f16045d220c69fe7dd5e68e14b2f9765fe011bf2 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 15:59:16 -0700 Subject: [PATCH 13/40] boop --- text/0000-dyn-error-generic-member-access.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index f53736feef2..d3834221d02 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -207,12 +207,15 @@ impl fmt::Debug for ErrorReporter { } ``` +As you can see the error trait provides the facilities needed to create error +reports enriched by information that may be present in source errors. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation The following changes need to be made to implement this proposal: -## Add a type like [`ObjectProvider::Request`] to std +### Add a type like [`ObjectProvider::Request`] to std This type fills the same role as `&dyn Any` except that it supports other trait objects as the requested type. @@ -331,7 +334,7 @@ pub struct FulfilledRequest(PhantomData<&'static Cell<()>>); pub type ProvideResult<'r, 'a> = Result, FulfilledRequest>; ``` -## Define a generic accessor on the `Error` trait +### Define a generic accessor on the `Error` trait ```rust pub trait Error { @@ -344,7 +347,7 @@ pub trait Error { } ``` -## Use this `Request` type to handle passing generic types out of the trait object +### Use this `Request` type to handle passing generic types out of the trait object ```rust impl dyn Error { From e12ba3586d7dd423564d6de012a7bc1d5c2b61c2 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:03:39 -0700 Subject: [PATCH 14/40] boop --- text/0000-dyn-error-generic-member-access.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d3834221d02..4e6faa670be 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -390,10 +390,9 @@ extract members. ## Use an alternative proposal that relies on the `Any` trait for downcasting -This approach is much simpler, but critically doesn't support providing -dynamically sized types, and it is more error prone because it cannot provide -compile time errors when the type you provide does not match the type_id you -were given. +This approach is simpler, but doesn't support providing dynamically sized +types and is more error prone because it cannot provide compile time errors +when the type you provide does not match the type_id you were given. ```rust pub trait Error { @@ -441,9 +440,6 @@ trait. # Future possibilities [future-possibilities]: #future-possibilities -Libraries like `thiserror` could add support for making members exportable as -context for reporters. - This opens the door to supporting `Error Return Traces`, similar to zigs, where if each return location is stored in a `Vec<&'static Location<'static>>` a full return trace could be built up with: @@ -457,3 +453,4 @@ let mut locations = e [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html [`ObjectProvider::Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs + From 9581f7835e3a1a4805d91be5c26028d0328fca4b Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:06:35 -0700 Subject: [PATCH 15/40] boop --- text/0000-dyn-error-generic-member-access.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 4e6faa670be..d1ce86e0054 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -8,14 +8,13 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern -used in `backtrace` and `source` and allows ecosystem iteration on error -reporting infrastructure outside of the standard library. This proposal adds -the method `Error::get_context` to the error trait, which offers `TypeId`-based -member lookup, and a new inherent fn `::context`, -which makes use of an implementor's `get_context` to return a typed reference -directly. These additions would primarily be useful in "error reporting" -contexts, where we typically no longer have type information and may be -composing errors from many sources. +used in `backtrace` and `source`. This proposal adds the method +`Error::get_context` to the error trait, which offers `TypeId`-based member +lookup, and a new inherent fn `::context`, which makes use of an +implementor's `get_context` to return a typed reference directly. These +additions would primarily be useful in "error reporting" contexts, where we +typically no longer have type information and may be composing errors from many +sources. ## TLDR From 6143f6d1203b9526318c511cb49dfd9c022eed30 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:10:20 -0700 Subject: [PATCH 16/40] boop --- text/0000-dyn-error-generic-member-access.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d1ce86e0054..48b7e949c7e 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -16,6 +16,10 @@ additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. +_note_: This RFC focuses on the more complicate of it's two proposed solutions +in order to support accessing DSTs. The [alternative proposal] is easier to +understand and may be more palatable. + ## TLDR Add this method to the `Error` trait @@ -452,4 +456,4 @@ let mut locations = e [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html [`ObjectProvider::Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs - +[alternative proposal]: #use-an-alternative-proposal-that-relies-on-the-any-trait-for-downcasting From 1bb8ca7bd5858e1263aa53895f19ecbb62c520e3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:27:33 -0700 Subject: [PATCH 17/40] boop --- text/0000-dyn-error-generic-member-access.md | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 48b7e949c7e..c17069a0d7a 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -16,7 +16,7 @@ additions would primarily be useful in "error reporting" contexts, where we typically no longer have type information and may be composing errors from many sources. -_note_: This RFC focuses on the more complicate of it's two proposed solutions +_note_: This RFC focuses on the more complicated of it's two proposed solutions in order to support accessing DSTs. The [alternative proposal] is easier to understand and may be more palatable. @@ -402,19 +402,34 @@ pub trait Error { /// Provide an untyped reference to a member whose type matches the provided `TypeId`. /// /// Returns `None` by default, implementors are encouraged to override. - fn provide(&self, ty: TypeId) -> Option<&dyn Any> { + fn get_context(&self, ty: TypeId) -> Option<&dyn Any> { None } } impl dyn Error { /// Retrieve a reference to `T`-typed context from the error if it is available. - pub fn request(&self) -> Option<&T> { + pub fn context(&self) -> Option<&T> { self.get_context(TypeId::of::())?.downcast_ref::() } } ``` +### Why isn't this the primary proposal? + +There are two big issues with using the `Any` trait that I believe justify the +more complicated solution. + +- You cannot return dynamically sized types as `&dyn Any` +- It's easy to introduce runtime errors with `&dyn Any` by either comparing to + or returning the wrong type + +By making all the type id comparison internal to the `Request` type it is +impossible to compare the wrong type ids. And by encouraging explicit type +parameters when calling `provide` the compiler is able to catch errors where +the type passed in doesn't match the type that was expected. So while the API +for the main proposal is more complicated it should be less error prone. + # Prior art [prior-art]: #prior-art From e82ac8294fcc9940c4782b52f4a2403deac140c8 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:35:01 -0700 Subject: [PATCH 18/40] boop --- text/0000-dyn-error-generic-member-access.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index c17069a0d7a..94012d25493 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -10,15 +10,15 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source`. This proposal adds the method `Error::get_context` to the error trait, which offers `TypeId`-based member -lookup, and a new inherent fn `::context`, which makes use of an +lookup and a new inherent fn `::context` which makes use of an implementor's `get_context` to return a typed reference directly. These -additions would primarily be useful in "error reporting" contexts, where we -typically no longer have type information and may be composing errors from many -sources. +additions would primarily be useful for error reporting, where we typically no +longer have type information and may be composing errors from many sources. -_note_: This RFC focuses on the more complicated of it's two proposed solutions -in order to support accessing DSTs. The [alternative proposal] is easier to -understand and may be more palatable. +_note_: This RFC focuses on the more complicated of it's two proposed +solutions. The proposed solution provides support accessing dynamically sized +types. The [alternative proposal] is easier to understand and may be more +palatable. ## TLDR From 53431f5b1652d625adc4147a1efc6412dd425376 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:46:45 -0700 Subject: [PATCH 19/40] boop --- text/0000-dyn-error-generic-member-access.md | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 94012d25493..4a7502884a5 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -1,4 +1,4 @@ -- Feature Name: Add fns for generic member access to dyn Error and the Error trait +- Feature Name: Add functions for generic member access to dyn Error and the Error trait - Start Date: 2020-04-01 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/2895) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -10,7 +10,7 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source`. This proposal adds the method `Error::get_context` to the error trait, which offers `TypeId`-based member -lookup and a new inherent fn `::context` which makes use of an +lookup and a new inherent function `::context` which makes use of an implementor's `get_context` to return a typed reference directly. These additions would primarily be useful for error reporting, where we typically no longer have type information and may be composing errors from many sources. @@ -85,14 +85,14 @@ many new forms of error reporting. gathered via `#[track_caller]` or similar. * error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused - when parsing a file + when parsing a file TODO reword * Help text such as suggestions or warnings attached to an error report ## Moving `Error` into `libcore` Adding a generic member access function to the Error trait and removing the -`backtrace` fn would make it possible to move the `Error` trait to libcore -without losing support for backtraces on std. The only difference would be that +`backtrace` function would make it possible to move the `Error` trait to libcore +without losing support for backtraces on std. The only difference being that in places where you can currently write `error.backtrace()` on nightly you would instead need to write `error.context::()`. @@ -109,10 +109,10 @@ constructing reports for end users while still retaining control over the format of the full report. The error trait accomplishes this by providing a set of methods for accessing -members of `dyn Error` trait objects. It requires types implement the display -trait, which acts as the interface to the main member, the error message -itself. It provides the `source` function for accessing `dyn Error` members, -which typically represent the current error's cause. It provides the +members of `dyn Error` trait objects. It requires that types implement the +display trait, which acts as the interface to the main member, the error +message itself. It provides the `source` function for accessing `dyn Error` +members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error report, the error trait provides the `context` and `get_context` functions. @@ -120,8 +120,9 @@ report, the error trait provides the `context` and `get_context` functions. As an example of how to use this interface to construct an error report, let’s explore how one could implement an error reporting type. In this example, our error reporting type will retrieve the source code location where each error in -the chain was created (if it exists) and render it as part of the chain of -errors. Our end goal is to get an error report that looks something like this: +the chain was created (if it captured a location) and render it as part of the +chain of errors. Our end goal is to get an error report that looks something +like this: ``` Error: @@ -363,7 +364,7 @@ impl dyn Error { # Drawbacks [drawbacks]: #drawbacks -* The `Request` api is being added purely to help with this fn, there may be +* The `Request` api is being added purely to help with this function, there may be some design iteration here that could be done to make this more generally applicable, it seems very similar to `dyn Any`. * The `context` function name is currently widely used throughout the rust From 6451b1c4104e416190c34bd9c5a1077c676627fc Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 16:58:01 -0700 Subject: [PATCH 20/40] boop --- text/0000-dyn-error-generic-member-access.md | 34 ++++++++------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 4a7502884a5..7ac2143453c 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -1,4 +1,4 @@ -- Feature Name: Add functions for generic member access to dyn Error and the Error trait +- Feature Name: Add functions for generic member access to dyn Error and the `Error` trait - Start Date: 2020-04-01 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/2895) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -9,7 +9,7 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source`. This proposal adds the method -`Error::get_context` to the error trait, which offers `TypeId`-based member +`Error::get_context` to the `Error` trait, which offers `TypeId`-based member lookup and a new inherent function `::context` which makes use of an implementor's `get_context` to return a typed reference directly. These additions would primarily be useful for error reporting, where we typically no @@ -90,7 +90,7 @@ many new forms of error reporting. ## Moving `Error` into `libcore` -Adding a generic member access function to the Error trait and removing the +Adding a generic member access function to the `Error` trait and removing the `backtrace` function would make it possible to move the `Error` trait to libcore without losing support for backtraces on std. The only difference being that in places where you can currently write `error.backtrace()` on nightly you @@ -108,14 +108,14 @@ allows error reporting types to handle errors in a consistent manner when constructing reports for end users while still retaining control over the format of the full report. -The error trait accomplishes this by providing a set of methods for accessing +The `Error` trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires that types implement the display trait, which acts as the interface to the main member, the error message itself. It provides the `source` function for accessing `dyn Error` members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error -report, the error trait provides the `context` and `get_context` functions. +report, the `Error` trait provides the `context` and `get_context` functions. As an example of how to use this interface to construct an error report, let’s explore how one could implement an error reporting type. In this example, our @@ -211,7 +211,7 @@ impl fmt::Debug for ErrorReporter { } ``` -As you can see the error trait provides the facilities needed to create error +As you can see the `Error` trait provides the facilities needed to create error reports enriched by information that may be present in source errors. # Reference-level explanation @@ -364,9 +364,9 @@ impl dyn Error { # Drawbacks [drawbacks]: #drawbacks -* The `Request` api is being added purely to help with this function, there may be - some design iteration here that could be done to make this more generally - applicable, it seems very similar to `dyn Any`. +* The `Request` api is being added purely to help with this function. This will + likely need some design work to make it more generally applicable, hopefully + as a struct in `core::any`. * The `context` function name is currently widely used throughout the rust error handling ecosystem in libraries like `anyhow` and `snafu` as an ergonomic version of `map_err`. If we settle on `context` as the final name @@ -376,8 +376,6 @@ impl dyn Error { # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -The two alternatives I can think of are: - ## Do Nothing We could not do this, and continue to add accessor functions to the `Error` @@ -385,7 +383,7 @@ trait whenever a new type reaches critical levels of popularity in error reporting. If we choose to do nothing we will continue to see hacks around the current -limitations on the error trait such as the `Fail` trait, which added the +limitations on the `Error` trait such as the `Fail` trait, which added the missing function access methods that didn't previously exist on the `Error` trait and type erasure / unnecessary boxing of errors to enable downcasting to extract members. @@ -394,10 +392,6 @@ extract members. ## Use an alternative proposal that relies on the `Any` trait for downcasting -This approach is simpler, but doesn't support providing dynamically sized -types and is more error prone because it cannot provide compile time errors -when the type you provide does not match the type_id you were given. - ```rust pub trait Error { /// Provide an untyped reference to a member whose type matches the provided `TypeId`. @@ -426,7 +420,7 @@ more complicated solution. or returning the wrong type By making all the type id comparison internal to the `Request` type it is -impossible to compare the wrong type ids. And by encouraging explicit type +impossible to compare the wrong type ids. By encouraging explicit type parameters when calling `provide` the compiler is able to catch errors where the type passed in doesn't match the type that was expected. So while the API for the main proposal is more complicated it should be less error prone. @@ -435,9 +429,9 @@ for the main proposal is more complicated it should be less error prone. [prior-art]: #prior-art I do not know of any other languages whose error handling has similar -facilities for accessing members when reporting errors. For the most part prior -art exists within rust itself in the form of previous additions to the `Error` -trait. +facilities for accessing members when reporting errors. For the most part, +prior art for this proposal comes from within rust itself in the form of +previous additions to the `Error` trait. # Unresolved questions [unresolved-questions]: #unresolved-questions From f74f64f60686c76a2b0f33721cea70473340b944 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 17:02:03 -0700 Subject: [PATCH 21/40] boop --- text/0000-dyn-error-generic-member-access.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 7ac2143453c..ce98173ffdf 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -440,11 +440,11 @@ previous additions to the `Error` trait. * `context`/`context_ref`/`get_context`/`provide_context` * `member`/`member_ref` * `provide`/`request` -* Should there be a by value version for accessing temporaries? +* Should there be a by-value version for accessing temporaries? * We bring this up specifically for the case where you want to use this - function to get an `Option<&[&dyn Error]>` out of an error, in this case - its unlikely that the error behind the trait object is actually storing - the errors as `dyn Errors`, and theres no easy way to allocate storage to + function to get an `Option<&[&dyn Error]>` out of an error, in this case, + it is unlikely that the error behind the trait object is actually storing + the errors as `dyn Error`s, and theres no easy way to allocate storage to store the trait objects. * How should context handle failed downcasts? * suggestion: panic, as providing a type that doesn't match the typeid From 75ef121962fb1222cc081ecf4388d568876eb9f5 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 17:18:31 -0700 Subject: [PATCH 22/40] fix attribution to be less confusing and mention source --- text/0000-dyn-error-generic-member-access.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index ce98173ffdf..07df8f89054 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -219,12 +219,13 @@ reports enriched by information that may be present in source errors. The following changes need to be made to implement this proposal: -### Add a type like [`ObjectProvider::Request`] to std +### Add a type like [`Request`] to std This type fills the same role as `&dyn Any` except that it supports other trait objects as the requested type. -Here is the implementation for the proof of concept: +Here is the implementation for the proof of concept, taken from Nika Layzell's +[object-provider crate]: ```rust /// A dynamic request for an object based on its type. @@ -465,5 +466,6 @@ let mut locations = e ``` [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html -[`ObjectProvider::Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs +[`Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs [alternative proposal]: #use-an-alternative-proposal-that-relies-on-the-any-trait-for-downcasting +[object-provider crate]: https://github.com/mystor/object-provider From ac0f94e8608841a08b818f7579dfec2dadc05bea Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 4 May 2020 17:23:31 -0700 Subject: [PATCH 23/40] nikanit --- text/0000-dyn-error-generic-member-access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 07df8f89054..4c54ca7a573 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -219,7 +219,7 @@ reports enriched by information that may be present in source errors. The following changes need to be made to implement this proposal: -### Add a type like [`Request`] to std +### Add a type like [`Request`] to core This type fills the same role as `&dyn Any` except that it supports other trait objects as the requested type. From 8d55678007ec4da455450c646edbe07a0bd3887d Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 5 May 2020 06:49:22 -0700 Subject: [PATCH 24/40] Update text/0000-dyn-error-generic-member-access.md Co-authored-by: kennytm --- text/0000-dyn-error-generic-member-access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 4c54ca7a573..d3a24a5cfce 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -44,7 +44,7 @@ fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, .provide::(&self.span_trace)? .provide::(&self.source)? .provide::>>(&self.locations)? - .provide::<[&'static Location<'static>>(&self.locations) + .provide::<[&'static Location<'static>]>(&self.locations) } ``` From 7f8754455b2fbf1c88a549e5870fdb8cb0c5ea0d Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 5 May 2020 06:51:12 -0700 Subject: [PATCH 25/40] rename to provide_context --- text/0000-dyn-error-generic-member-access.md | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d3a24a5cfce..62cc37d2034 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -9,9 +9,9 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source`. This proposal adds the method -`Error::get_context` to the `Error` trait, which offers `TypeId`-based member +`Error::provide_context` to the `Error` trait, which offers `TypeId`-based member lookup and a new inherent function `::context` which makes use of an -implementor's `get_context` to return a typed reference directly. These +implementor's `provide_context` to return a typed reference directly. These additions would primarily be useful for error reporting, where we typically no longer have type information and may be composing errors from many sources. @@ -29,7 +29,7 @@ pub trait Error { // ... /// Provides an object of type `T` in response to this request. - fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { Ok(request) } } @@ -38,7 +38,7 @@ pub trait Error { Example implementation: ```rust -fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { +fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { request .provide::(&self.backtrace)? .provide::(&self.span_trace)? @@ -115,7 +115,7 @@ message itself. It provides the `source` function for accessing `dyn Error` members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error -report, the `Error` trait provides the `context` and `get_context` functions. +report, the `Error` trait provides the `context` and `provide_context` functions. As an example of how to use this interface to construct an error report, let’s explore how one could implement an error reporting type. In this example, our @@ -178,7 +178,7 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { request.provide::(&self.location) } } @@ -346,7 +346,7 @@ pub trait Error { // ... /// Provides an object of type `T` in response to this request. - fn get_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { + fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { Ok(request) } } @@ -357,7 +357,7 @@ pub trait Error { ```rust impl dyn Error { pub fn context(&self) -> Option<&T> { - Request::with::(|req| self.get_context(req)) + Request::with::(|req| self.provide_context(req)) } } ``` @@ -398,7 +398,7 @@ pub trait Error { /// Provide an untyped reference to a member whose type matches the provided `TypeId`. /// /// Returns `None` by default, implementors are encouraged to override. - fn get_context(&self, ty: TypeId) -> Option<&dyn Any> { + fn provide_context(&self, ty: TypeId) -> Option<&dyn Any> { None } } @@ -406,7 +406,7 @@ pub trait Error { impl dyn Error { /// Retrieve a reference to `T`-typed context from the error if it is available. pub fn context(&self) -> Option<&T> { - self.get_context(TypeId::of::())?.downcast_ref::() + self.provide_context(TypeId::of::())?.downcast_ref::() } } ``` @@ -438,7 +438,7 @@ previous additions to the `Error` trait. [unresolved-questions]: #unresolved-questions * What should the names of these functions be? - * `context`/`context_ref`/`get_context`/`provide_context` + * `context`/`context_ref`/`provide_context`/`provide_context` * `member`/`member_ref` * `provide`/`request` * Should there be a by-value version for accessing temporaries? From bcb4823f50c06e30a26515ec41614b19f0c98c00 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 5 May 2020 10:19:11 -0700 Subject: [PATCH 26/40] Update based on kennys changes --- text/0000-dyn-error-generic-member-access.md | 118 ++++++++----------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 62cc37d2034..9ea8fe6e6f6 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -29,22 +29,20 @@ pub trait Error { // ... /// Provides an object of type `T` in response to this request. - fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { - Ok(request) - } + fn provide_context<'a>(&'a self, request: Pin<&mut Request<'a>>) {} } ``` Example implementation: ```rust -fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { +fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { request - .provide::(&self.backtrace)? - .provide::(&self.span_trace)? - .provide::(&self.source)? - .provide::>>(&self.locations)? - .provide::<[&'static Location<'static>]>(&self.locations) + .provide::(&self.backtrace) + .provide::(&self.span_trace) + .provide::(&self.source) + .provide::>>(&self.locations) + .provide::<[&'static Location<'static>]>(&self.locations); } ``` @@ -178,8 +176,8 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { - request.provide::(&self.location) + fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { + request.provide::(&self.location); } } ``` @@ -228,16 +226,23 @@ Here is the implementation for the proof of concept, taken from Nika Layzell's [object-provider crate]: ```rust +use core::any::TypeId; +use core::cell::Cell; +use core::fmt; +use core::marker::PhantomData; +use core::pin::Pin; + /// A dynamic request for an object based on its type. /// -/// `'r` is the lifetime of request, and `'out` is the lifetime of the requested -/// reference. -pub struct Request<'r, 'out> { - buf: NonNull, - _marker: PhantomData<&'r mut &'out Cell<()>>, -} - -impl<'r, 'out> Request<'r, 'out> { +/// `'out` is the lifetime of the requested reference. +#[repr(transparent)] +pub struct Request<'out>(RequestBuf>>); +// FIXME: The argument of the RequestBuf should be a thin unsized type, +// but `extern type` is impossible to use correctly right now +// (it cannot be placed at offset > 0, and it cannot be placed inside a union). +// Since miri doesn't complain we'll let it slide. + +impl<'out> Request<'out> { /// Provides an object of type `T` in response to this request. /// /// Returns `Err(FulfilledRequest)` if the value was successfully provided, @@ -245,7 +250,7 @@ impl<'r, 'out> Request<'r, 'out> { /// /// This method can be chained within `provide` implementations using the /// `?` operator to concisely provide multiple objects. - pub fn provide(self, value: &'out T) -> ProvideResult<'r, 'out> { + pub fn provide(&mut self, value: &'out T) -> &mut Self { self.provide_with(|| value) } @@ -259,26 +264,24 @@ impl<'r, 'out> Request<'r, 'out> { /// /// This method can be chained within `provide` implementations using the /// `?` operator to concisely provide multiple objects. - pub fn provide_with(mut self, cb: F) -> ProvideResult<'r, 'out> + pub fn provide_with(&mut self, cb: F) -> &mut Self where F: FnOnce() -> &'out T, { - match self.downcast_buf::() { - Some(this) => { - debug_assert!( - this.value.is_none(), - "Multiple requests to a `RequestBuf` were acquired?" - ); - this.value = Some(cb()); - Err(FulfilledRequest(PhantomData)) - } - None => Ok(self), + if self.is::() { + let this = unsafe { &mut *(self as *mut _ as *mut RequestBuf>) }; + debug_assert!( + this.value.is_none(), + "Multiple requests to a `RequestBuf` were acquired?" + ); + this.value = Some(cb()); } + self } /// Get the `TypeId` of the requested type. pub fn type_id(&self) -> TypeId { - unsafe { *self.buf.as_ref() } + self.0.type_id } /// Returns `true` if the requested type is the same as `T` @@ -286,19 +289,6 @@ impl<'r, 'out> Request<'r, 'out> { self.type_id() == TypeId::of::() } - /// Try to downcast this `Request` into a reference to the typed - /// `RequestBuf` object. - /// - /// This method will return `None` if `self` was not derived from a - /// `RequestBuf<'_, T>`. - fn downcast_buf(&mut self) -> Option<&mut RequestBuf<'out, T>> { - if self.is::() { - unsafe { Some(&mut *(self.buf.as_ptr() as *mut RequestBuf<'out, T>)) } - } else { - None - } - } - /// Calls the provided closure with a request for the the type `T`, returning /// `Some(&T)` if the request was fulfilled, and `None` otherwise. /// @@ -306,37 +296,34 @@ impl<'r, 'out> Request<'r, 'out> { /// types implementing `ObjectProvider`. pub fn with(f: F) -> Option<&'out T> where - for<'a> F: FnOnce(Request<'a, 'out>) -> ProvideResult<'a, 'out>, + F: FnOnce(Pin<&mut Self>), { let mut buf = RequestBuf { type_id: TypeId::of::(), value: None, }; - let _ = f(Request { - buf: unsafe { - NonNull::new_unchecked(&mut buf as *mut RequestBuf<'out, T> as *mut TypeId) - }, - _marker: PhantomData, - }); + unsafe { + let request = &mut *(&mut buf as *mut _ as *mut Request); + f(Pin::new(request)); + } buf.value } } +impl<'out> fmt::Debug for Request<'out> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Request") + .field("type_id", &self.type_id()) + .finish() + } +} + // Needs to have a known layout so we can do unsafe pointer shenanigans. #[repr(C)] -struct RequestBuf<'a, T: ?Sized> { +struct RequestBuf { type_id: TypeId, - value: Option<&'a T>, + value: T, } - -/// Marker type indicating a request has been fulfilled. -pub struct FulfilledRequest(PhantomData<&'static Cell<()>>); - -/// Provider method return type. -/// -/// Either `Ok(Request)` for an unfulfilled request, or `Err(FulfilledRequest)` -/// if the request was fulfilled. -pub type ProvideResult<'r, 'a> = Result, FulfilledRequest>; ``` ### Define a generic accessor on the `Error` trait @@ -345,10 +332,7 @@ pub type ProvideResult<'r, 'a> = Result, FulfilledRequest>; pub trait Error { // ... - /// Provides an object of type `T` in response to this request. - fn provide_context<'r, 'a>(&'a self, request: Request<'r, 'a>) -> ProvideResult<'r, 'a> { - Ok(request) - } + fn provide_context<'a>(&'a self, _request: Pin<&mut Request<'a>>) {} } ``` From 2ea013dfa3295d6b166ecb2093e6f5c2e9cd8ba3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 5 May 2020 10:21:36 -0700 Subject: [PATCH 27/40] Document divergence from object-provider crate --- text/0000-dyn-error-generic-member-access.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 9ea8fe6e6f6..6dc3d78952d 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -222,9 +222,12 @@ The following changes need to be made to implement this proposal: This type fills the same role as `&dyn Any` except that it supports other trait objects as the requested type. -Here is the implementation for the proof of concept, taken from Nika Layzell's +Here is the implementation for the proof of concept, based on Nika Layzell's [object-provider crate]: +A usable version of this is available in the [proof of concept] repo under +`fakecore/src/any.rs`. + ```rust use core::any::TypeId; use core::cell::Cell; @@ -453,3 +456,4 @@ let mut locations = e [`Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs [alternative proposal]: #use-an-alternative-proposal-that-relies-on-the-any-trait-for-downcasting [object-provider crate]: https://github.com/mystor/object-provider +[proof of concept]: https://github.com/yaahc/nostd-error-poc From 248e4ca26d919fad2f98f6475bee79278b9dfe19 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 5 May 2020 10:38:01 -0700 Subject: [PATCH 28/40] Add example to code snippet --- text/0000-dyn-error-generic-member-access.md | 40 +++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 6dc3d78952d..f9dfde1f682 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -28,7 +28,45 @@ Add this method to the `Error` trait pub trait Error { // ... - /// Provides an object of type `T` in response to this request. + /// Provides type based access to context intended for error reports + /// + /// Used in conjunction with [`context`] to extract references to member variables from `dyn + /// Error` trait objects. + /// + /// # Example + /// + /// ```rust + /// use core::pin::Pin; + /// use backtrace::Backtrace; + /// use core::fmt; + /// use fakecore::any::Request; + /// + /// #[derive(Debug)] + /// struct Error { + /// backtrace: Backtrace, + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// write!(f, "Example Error") + /// } + /// } + /// + /// impl fakecore::error::Error for Error { + /// fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { + /// request.provide::(&self.backtrace); + /// } + /// } + /// + /// fn main() { + /// let backtrace = Backtrace::new(); + /// let error = Error { backtrace }; + /// let dyn_error = &error as &dyn fakecore::error::Error; + /// let backtrace_ref = dyn_error.context::().unwrap(); + /// + /// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); + /// } + /// ``` fn provide_context<'a>(&'a self, request: Pin<&mut Request<'a>>) {} } ``` From ef2e47e9de26c8ecf83ddfeece9817a7b877a5a0 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Wed, 6 May 2020 10:04:39 -0700 Subject: [PATCH 29/40] update to include nikas updates --- text/0000-dyn-error-generic-member-access.md | 126 ++++++++++++------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index f9dfde1f682..0959896d635 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -268,61 +268,55 @@ A usable version of this is available in the [proof of concept] repo under ```rust use core::any::TypeId; -use core::cell::Cell; use core::fmt; -use core::marker::PhantomData; +use core::marker::{PhantomData, PhantomPinned}; use core::pin::Pin; /// A dynamic request for an object based on its type. -/// -/// `'out` is the lifetime of the requested reference. -#[repr(transparent)] -pub struct Request<'out>(RequestBuf>>); -// FIXME: The argument of the RequestBuf should be a thin unsized type, -// but `extern type` is impossible to use correctly right now -// (it cannot be placed at offset > 0, and it cannot be placed inside a union). -// Since miri doesn't complain we'll let it slide. - -impl<'out> Request<'out> { +#[repr(C)] +pub struct Request<'a> { + type_id: TypeId, + _pinned: PhantomPinned, + _marker: PhantomData<&'a ()>, +} + +impl<'a> Request<'a> { /// Provides an object of type `T` in response to this request. /// - /// Returns `Err(FulfilledRequest)` if the value was successfully provided, - /// and `Ok(self)` if `T` was not the type being requested. + /// If an object of type `T` has already been provided for this request, the + /// existing value will be replaced by the newly provided value. /// - /// This method can be chained within `provide` implementations using the - /// `?` operator to concisely provide multiple objects. - pub fn provide(&mut self, value: &'out T) -> &mut Self { + /// This method can be chained within `provide` implementations to concisely + /// provide multiple objects. + pub fn provide(self: Pin<&mut Self>, value: &'a T) -> Pin<&mut Self> { self.provide_with(|| value) } /// Lazily provides an object of type `T` in response to this request. /// - /// Returns `Err(FulfilledRequest)` if the value was successfully provided, - /// and `Ok(self)` if `T` was not the type being requested. + /// If an object of type `T` has already been provided for this request, the + /// existing value will be replaced by the newly provided value. /// /// The passed closure is only called if the value will be successfully /// provided. /// - /// This method can be chained within `provide` implementations using the - /// `?` operator to concisely provide multiple objects. - pub fn provide_with(&mut self, cb: F) -> &mut Self + /// This method can be chained within `provide` implementations to concisely + /// provide multiple objects. + pub fn provide_with(mut self: Pin<&mut Self>, cb: F) -> Pin<&mut Self> where - F: FnOnce() -> &'out T, + F: FnOnce() -> &'a T, { - if self.is::() { - let this = unsafe { &mut *(self as *mut _ as *mut RequestBuf>) }; - debug_assert!( - this.value.is_none(), - "Multiple requests to a `RequestBuf` were acquired?" - ); - this.value = Some(cb()); + if let Some(buf) = self.as_mut().downcast_buf::() { + // NOTE: We could've already provided a value here of type `T`, + // which will be clobbered in this case. + *buf = Some(cb()); } self } /// Get the `TypeId` of the requested type. pub fn type_id(&self) -> TypeId { - self.0.type_id + self.type_id } /// Returns `true` if the requested type is the same as `T` @@ -330,28 +324,41 @@ impl<'out> Request<'out> { self.type_id() == TypeId::of::() } + /// Try to downcast this `Request` into a reference to the typed + /// `RequestBuf` object, and access the trailing `Option<&'a T>`. + /// + /// This method will return `None` if `self` is not the prefix of a + /// `RequestBuf<'_, T>`. + fn downcast_buf(self: Pin<&mut Self>) -> Option<&mut Option<&'a T>> { + if self.is::() { + // Safety: `self` is pinned, meaning it exists as the first + // field within our `RequestBuf`. As the type matches, and + // `RequestBuf` has a known in-memory layout, this downcast is + // sound. + unsafe { + let ptr = self.get_unchecked_mut() as *mut Self as *mut RequestBuf<'a, T>; + Some(&mut (*ptr).value) + } + } else { + None + } + } + /// Calls the provided closure with a request for the the type `T`, returning /// `Some(&T)` if the request was fulfilled, and `None` otherwise. - /// - /// The `ObjectProviderExt` trait provides helper methods specifically for - /// types implementing `ObjectProvider`. - pub fn with(f: F) -> Option<&'out T> + pub fn with(f: F) -> Option<&'a T> where F: FnOnce(Pin<&mut Self>), { - let mut buf = RequestBuf { - type_id: TypeId::of::(), - value: None, - }; - unsafe { - let request = &mut *(&mut buf as *mut _ as *mut Request); - f(Pin::new(request)); - } - buf.value + let mut buf = RequestBuf::new(); + // safety: We never move `buf` after creating `pinned`. + let mut pinned = unsafe { Pin::new_unchecked(&mut buf) }; + f(pinned.as_mut().request()); + pinned.take() } } -impl<'out> fmt::Debug for Request<'out> { +impl<'a> fmt::Debug for Request<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Request") .field("type_id", &self.type_id()) @@ -361,9 +368,32 @@ impl<'out> fmt::Debug for Request<'out> { // Needs to have a known layout so we can do unsafe pointer shenanigans. #[repr(C)] -struct RequestBuf { - type_id: TypeId, - value: T, +struct RequestBuf<'a, T: ?Sized + 'static> { + request: Request<'a>, + value: Option<&'a T>, +} + +impl<'a, T: ?Sized + 'static> RequestBuf<'a, T> { + fn new() -> Self { + RequestBuf { + request: Request { + type_id: TypeId::of::(), + _pinned: PhantomPinned, + _marker: PhantomData, + }, + value: None, + } + } + + fn request(self: Pin<&mut Self>) -> Pin<&mut Request<'a>> { + // safety: projecting Pin onto our `request` field. + unsafe { self.map_unchecked_mut(|this| &mut this.request) } + } + + fn take(self: Pin<&mut Self>) -> Option<&'a T> { + // safety: `Option<&'a T>` is `Unpin` + unsafe { self.get_unchecked_mut().value.take() } + } } ``` From d055bbbe9a988bca4cb962e4002cac72daeb3da0 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 7 May 2020 11:52:05 -0700 Subject: [PATCH 30/40] Update text/0000-dyn-error-generic-member-access.md Co-authored-by: Adam Perry --- text/0000-dyn-error-generic-member-access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 0959896d635..ca654ea7937 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -137,7 +137,7 @@ would instead need to write `error.context::()`. Error handling in Rust consists of three steps: creation/propagation, handling, and reporting. The `std::error::Error` trait exists to bridge the gap between -creation and reporting. It does so by acting as a interface that all error +creation and reporting. It does so by acting as an interface that all error types can implement that defines how to access context intended for error reports, such as the error message, source, or location it was created. This allows error reporting types to handle errors in a consistent manner when From ac798142d87251f7b25c8470a7f40bdb3c0b2222 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 7 May 2020 11:52:54 -0700 Subject: [PATCH 31/40] Apply suggestions from code review Co-authored-by: Adam Perry --- text/0000-dyn-error-generic-member-access.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index ca654ea7937..37a20e8ea6a 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -146,7 +146,7 @@ format of the full report. The `Error` trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires that types implement the -display trait, which acts as the interface to the main member, the error +`Display` trait, which acts as the interface to the main member, the error message itself. It provides the `source` function for accessing `dyn Error` members, which typically represent the current error's cause. It provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack @@ -468,15 +468,15 @@ impl dyn Error { ### Why isn't this the primary proposal? -There are two big issues with using the `Any` trait that I believe justify the +There are two significant issues with using the `Any` trait that motivate the more complicated solution. - You cannot return dynamically sized types as `&dyn Any` - It's easy to introduce runtime errors with `&dyn Any` by either comparing to or returning the wrong type -By making all the type id comparison internal to the `Request` type it is -impossible to compare the wrong type ids. By encouraging explicit type +By making all the `TypeId` comparison internal to the `Request` type it is +impossible to compare the wrong `TypeId`s. By encouraging explicit type parameters when calling `provide` the compiler is able to catch errors where the type passed in doesn't match the type that was expected. So while the API for the main proposal is more complicated it should be less error prone. From 41b589bf81874e5b3d55f576118e44a6f5f85eab Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 11 May 2020 11:34:56 -0700 Subject: [PATCH 32/40] reply to adams coments --- text/0000-dyn-error-generic-member-access.md | 76 ++++++++++++++------ 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 37a20e8ea6a..0da06d86403 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -71,6 +71,17 @@ pub trait Error { } ``` +And this inherent method on `dyn Error` trait objects: + +```rust +impl dyn Error { + pub fn context(&self) -> Option<&T> { + Request::with::(|req| self.provide_context(req)) + } +} +``` + + Example implementation: ```rust @@ -127,10 +138,11 @@ many new forms of error reporting. ## Moving `Error` into `libcore` Adding a generic member access function to the `Error` trait and removing the -`backtrace` function would make it possible to move the `Error` trait to libcore -without losing support for backtraces on std. The only difference being that -in places where you can currently write `error.backtrace()` on nightly you -would instead need to write `error.context::()`. +currently unstable `backtrace` function would make it possible to move the +`Error` trait to libcore without losing support for backtraces on std. The only +difference being that in places where you can currently write +`error.backtrace()` on nightly you would instead need to write +`error.context::()`. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -148,10 +160,11 @@ The `Error` trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires that types implement the `Display` trait, which acts as the interface to the main member, the error message itself. It provides the `source` function for accessing `dyn Error` -members, which typically represent the current error's cause. It provides the -`backtrace` function, for accessing a `Backtrace` of the state of the stack -when an error was created. For all other forms of context relevant to an error -report, the `Error` trait provides the `context` and `provide_context` functions. +members, which typically represent the current error's cause. Via +`#![feature(backtrace)]` it provides the `backtrace` function, for accessing a +`Backtrace` of the state of the stack when an error was created. For all other +forms of context relevant to an error report, the `Error` trait provides the +`context` and `provide_context` functions. As an example of how to use this interface to construct an error report, let’s explore how one could implement an error reporting type. In this example, our @@ -162,9 +175,12 @@ like this: ``` Error: - 0: Failed to read instrs from ./path/to/instrs.json - at instrs.rs:42 - 1: No such file or directory (os error 2) + 0: ERROR MESSAGE + at LOCATION + 1: SOURCE'S ERROR MESSAGE + at SOURCE'S LOCATION + 2: SOURCE'S SOURCE'S ERROR MESSAGE + ... ``` The first step is to define or use a type to represent a source location. In @@ -247,18 +263,41 @@ impl fmt::Debug for ErrorReporter { } ``` -As you can see the `Error` trait provides the facilities needed to create error -reports enriched by information that may be present in source errors. +Now we have an error reporter that is ready for use, a simple program using it +would look like this. + +```rust +fn main() -> Result<(), ErrorReporter> { + let path = "./path/to/instrs.json"; + let _instrs = read_instrs(path.into())?; +} +``` + +Which, if run without creating the `instrs.json` file prints this error report: + +``` +Error: + 0: Failed to read instrs from ./path/to/instrs.json + at instrs.rs:42 + 1: No such file or directory (os error 2) +``` + +Mission accomplished! The error trait gave us everything we needed to build +error reports enriched by context relevant to our application. This same +pattern can be implement many error reporting patterns, such as including help +text, spans, http status codes, or backtraces in errors which are still +accessible after the error has been converted to a `dyn Error`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation The following changes need to be made to implement this proposal: -### Add a type like [`Request`] to core +### Add a `Request` type to `libcore` for type-indexed context -This type fills the same role as `&dyn Any` except that it supports other trait -objects as the requested type. +`Request` is a type to emulate nested dynamic typing. This type fills the same +role as `&dyn Any` except that it supports other trait objects as the requested +type. Here is the implementation for the proof of concept, based on Nika Layzell's [object-provider crate]: @@ -493,7 +532,7 @@ previous additions to the `Error` trait. [unresolved-questions]: #unresolved-questions * What should the names of these functions be? - * `context`/`context_ref`/`provide_context`/`provide_context` + * `context`/`context_ref`/`provide_context`/`provide_context`/`request_context` * `member`/`member_ref` * `provide`/`request` * Should there be a by-value version for accessing temporaries? @@ -502,9 +541,6 @@ previous additions to the `Error` trait. it is unlikely that the error behind the trait object is actually storing the errors as `dyn Error`s, and theres no easy way to allocate storage to store the trait objects. -* How should context handle failed downcasts? - * suggestion: panic, as providing a type that doesn't match the typeid - requested is a program error # Future possibilities [future-possibilities]: #future-possibilities From defeafeb28b28a4bea50fc4747313c00c67f4fbc Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 11 May 2020 11:36:23 -0700 Subject: [PATCH 33/40] make type_id fn in Request private --- text/0000-dyn-error-generic-member-access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 0da06d86403..4902ab5ee6e 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -354,7 +354,7 @@ impl<'a> Request<'a> { } /// Get the `TypeId` of the requested type. - pub fn type_id(&self) -> TypeId { + fn type_id(&self) -> TypeId { self.type_id } From 48adce6ad3410ba3215fc8671cb2b7b7f1e120ae Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 11 May 2020 12:05:58 -0700 Subject: [PATCH 34/40] remove type_id fn --- text/0000-dyn-error-generic-member-access.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 4902ab5ee6e..d062076efe6 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -353,14 +353,9 @@ impl<'a> Request<'a> { self } - /// Get the `TypeId` of the requested type. - fn type_id(&self) -> TypeId { - self.type_id - } - /// Returns `true` if the requested type is the same as `T` pub fn is(&self) -> bool { - self.type_id() == TypeId::of::() + self.type_id == TypeId::of::() } /// Try to downcast this `Request` into a reference to the typed @@ -400,7 +395,7 @@ impl<'a> Request<'a> { impl<'a> fmt::Debug for Request<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Request") - .field("type_id", &self.type_id()) + .field("type_id", &self.type_id) .finish() } } From 0d441ac2c080db8aeb23ac6eb30fd537596bf7e8 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 28 Jul 2020 10:16:24 -0700 Subject: [PATCH 35/40] update example to use successors --- text/0000-dyn-error-generic-member-access.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index d062076efe6..feadc388215 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -244,18 +244,13 @@ struct ErrorReporter(Box); impl fmt::Debug for ErrorReporter { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut current_error = Some(self.0.as_ref()); - let mut ind = 0; - - while let Some(error) = current_error { - writeln!(fmt, " {}: {}", ind, error)?; + let error: &(dyn Error + 'static) = self.0.as_ref(); + let errors = std::iter::successors(Some(error), |e| e.source()); + for (ind, error) in errors.enumerate() { if let Some(location) = error.context::() { writeln!(fmt, " at {}:{}", location.file, location.line)?; } - - ind += 1; - current_error = error.source(); } Ok(()) From 7b9876037f335a9c99c6ed6f340c97567d46c6e6 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Tue, 28 Jul 2020 10:18:23 -0700 Subject: [PATCH 36/40] add back missing write --- text/0000-dyn-error-generic-member-access.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index feadc388215..867dc560136 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -248,6 +248,7 @@ impl fmt::Debug for ErrorReporter { let errors = std::iter::successors(Some(error), |e| e.source()); for (ind, error) in errors.enumerate() { + writeln!(fmt, " {}: {}", ind, error)?; if let Some(location) = error.context::() { writeln!(fmt, " at {}:{}", location.file, location.line)?; } From 407ce78fba7b85b6948c8a5973df2025c944eca3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Fri, 4 Dec 2020 10:21:54 -0800 Subject: [PATCH 37/40] update rfc to include new object provider API --- text/0000-dyn-error-generic-member-access.md | 462 +++++++++++++------ 1 file changed, 329 insertions(+), 133 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 867dc560136..620a8905fe1 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -9,16 +9,12 @@ This RFC proposes additions to the `Error` trait to support accessing generic forms of context from `dyn Error` trait objects. This generalizes the pattern used in `backtrace` and `source`. This proposal adds the method -`Error::provide_context` to the `Error` trait, which offers `TypeId`-based member -lookup and a new inherent function `::context` which makes use of an -implementor's `provide_context` to return a typed reference directly. These -additions would primarily be useful for error reporting, where we typically no -longer have type information and may be composing errors from many sources. - -_note_: This RFC focuses on the more complicated of it's two proposed -solutions. The proposed solution provides support accessing dynamically sized -types. The [alternative proposal] is easier to understand and may be more -palatable. +`Error::provide_context` to the `Error` trait, which offers `TypeId`-based +member lookup and a new inherent function `::context` and `::context_ref` which makes use of an implementor's `provide_context` to +return a typed reference directly. These additions would primarily be useful +for error reporting, where we typically no longer have type information and +may be composing errors from many sources. ## TLDR @@ -30,13 +26,12 @@ pub trait Error { /// Provides type based access to context intended for error reports /// - /// Used in conjunction with [`context`] to extract references to member variables from `dyn - /// Error` trait objects. + /// Used in conjunction with [`context`] and [`context_ref`] to extract + /// references to member variables from `dyn Error` trait objects. /// /// # Example /// /// ```rust - /// use core::pin::Pin; /// use backtrace::Backtrace; /// use core::fmt; /// use fakecore::any::Request; @@ -53,8 +48,8 @@ pub trait Error { /// } /// /// impl fakecore::error::Error for Error { - /// fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { - /// request.provide::(&self.backtrace); + /// fn provide_context<'a>(&'a self, mut request: &mut Request<'a>) { + /// request.provide_ref::(&self.backtrace); /// } /// } /// @@ -62,36 +57,39 @@ pub trait Error { /// let backtrace = Backtrace::new(); /// let error = Error { backtrace }; /// let dyn_error = &error as &dyn fakecore::error::Error; - /// let backtrace_ref = dyn_error.context::().unwrap(); + /// let backtrace_ref = dyn_error.context_ref::().unwrap(); /// /// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); /// } /// ``` - fn provide_context<'a>(&'a self, request: Pin<&mut Request<'a>>) {} + fn provide_context<'a>(&'a self, request: &mut Request<'a>) {} } ``` -And this inherent method on `dyn Error` trait objects: +And these inherent methods on `dyn Error` trait objects: ```rust impl dyn Error { - pub fn context(&self) -> Option<&T> { - Request::with::(|req| self.provide_context(req)) + pub fn context_ref(&self) -> Option<&T> { + Request::request_ref(|req| self.provide_context(req)) + } + + pub fn context(&self) -> Option { + Request::request_value(|req| self.provide_context(req)) } } ``` - Example implementation: ```rust -fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { +fn provide_context<'a>(&'a self, mut request: &mut Request<'a>) { request - .provide::(&self.backtrace) - .provide::(&self.span_trace) - .provide::(&self.source) - .provide::>>(&self.locations) - .provide::<[&'static Location<'static>]>(&self.locations); + .provide_ref::(&self.backtrace) + .provide_ref::(&self.span_trace) + .provide_ref::(&self.source) + .provide_ref::>>(&self.locations) + .provide_ref::<[&'static Location<'static>]>(&self.locations); } ``` @@ -100,7 +98,7 @@ Example usage: ```rust let e: &dyn Error = &concrete_error; -if let Some(bt) = e.context::() { +if let Some(bt) = e.context_ref::() { println!("{}", bt); } ``` @@ -135,26 +133,17 @@ many new forms of error reporting. when parsing a file TODO reword * Help text such as suggestions or warnings attached to an error report -## Moving `Error` into `libcore` - -Adding a generic member access function to the `Error` trait and removing the -currently unstable `backtrace` function would make it possible to move the -`Error` trait to libcore without losing support for backtraces on std. The only -difference being that in places where you can currently write -`error.backtrace()` on nightly you would instead need to write -`error.context::()`. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Error handling in Rust consists of three steps: creation/propagation, handling, -and reporting. The `std::error::Error` trait exists to bridge the gap between -creation and reporting. It does so by acting as an interface that all error -types can implement that defines how to access context intended for error -reports, such as the error message, source, or location it was created. This -allows error reporting types to handle errors in a consistent manner when -constructing reports for end users while still retaining control over the -format of the full report. +Error handling in Rust consists of three main steps: creation/propagation, +handling, and reporting. The `std::error::Error` trait exists to bridge the +gap between creation and reporting. It does so by acting as an interface that +all error types can implement that defines how to access context intended for +error reports, such as the error message, source, or location it was created. +This allows error reporting types to handle errors in a consistent manner +when constructing reports for end users while still retaining control over +the format of the full report. The `Error` trait accomplishes this by providing a set of methods for accessing members of `dyn Error` trait objects. It requires that types implement the @@ -164,7 +153,7 @@ members, which typically represent the current error's cause. Via `#![feature(backtrace)]` it provides the `backtrace` function, for accessing a `Backtrace` of the state of the stack when an error was created. For all other forms of context relevant to an error report, the `Error` trait provides the -`context` and `provide_context` functions. +`context`, context_ref`, and `provide_context` functions. As an example of how to use this interface to construct an error report, let’s explore how one could implement an error reporting type. In this example, our @@ -230,8 +219,8 @@ impl std::error::Error for ExampleError { Some(&self.source) } - fn provide_context<'a>(&'a self, mut request: Pin<&mut Request<'a>>) { - request.provide::(&self.location); + fn provide_context<'a>(&'a self, mut request: &mut Request<'a>) { + request.provide_ref::(&self.location); } } ``` @@ -249,7 +238,7 @@ impl fmt::Debug for ErrorReporter { for (ind, error) in errors.enumerate() { writeln!(fmt, " {}: {}", ind, error)?; - if let Some(location) = error.context::() { + if let Some(location) = error.context_ref::() { writeln!(fmt, " at {}:{}", location.file, location.line)?; } } @@ -303,128 +292,327 @@ A usable version of this is available in the [proof of concept] repo under ```rust use core::any::TypeId; -use core::fmt; -use core::marker::{PhantomData, PhantomPinned}; -use core::pin::Pin; +use core::marker::PhantomData; + +mod private { + pub trait Response<'a>: 'a {} +} + +/// A response to a ref request. +struct RefResponse<'a, T: ?Sized + 'static>(Option<&'a T>); +impl<'a, T: ?Sized + 'static> private::Response<'a> for RefResponse<'a, T> {} + +/// A response to a value request. +struct ValueResponse(Option); +impl<'a, T: 'static> private::Response<'a> for ValueResponse {} /// A dynamic request for an object based on its type. -#[repr(C)] -pub struct Request<'a> { +pub struct Request<'a, R = dyn private::Response<'a>> +where + R: ?Sized + private::Response<'a>, +{ + marker: PhantomData<&'a ()>, + + /// A `TypeId` marker for the type stored in `R`. + /// + /// Will be the TypeId of either `RefResponse<'static, T>` or + /// `ValueResponse`. type_id: TypeId, - _pinned: PhantomPinned, - _marker: PhantomData<&'a ()>, + + /// A type erased `RefResponse` or `ValueResponse` containing the response + /// value. + response: R, } impl<'a> Request<'a> { - /// Provides an object of type `T` in response to this request. + /// Perform a checked downcast of `response` to `Option<&'a T>` + fn downcast_ref_response<'b, T: ?Sized + 'static>( + &'b mut self, + ) -> Option<&'b mut RefResponse<'a, T>> { + if self.is_ref::() { + // safety: If `self.is_ref::()` returns true, `response` must be + // of the correct type. This is enforced by the private `type_id` + // field. + Some(unsafe { &mut *(&mut self.response as *mut _ as *mut RefResponse<'a, T>) }) + } else { + None + } + } + + /// Perform a checked downcast of `response` to `Option` + fn downcast_value_response<'b, T: 'static>(&'b mut self) -> Option<&'b mut ValueResponse> { + if self.is_value::() { + // safety: If `self.is_value::()` returns true, `response` must + // be of the correct type. This is enforced by the private `type_id` + // field. + Some(unsafe { &mut *(&mut self.response as *mut _ as *mut ValueResponse) }) + } else { + None + } + } + + /// Provides a reference of type `&'a T` in response to this request. /// - /// If an object of type `T` has already been provided for this request, the - /// existing value will be replaced by the newly provided value. + /// If a reference of type `&'a T` has already been provided for this + /// request, or if the request is for a different type, this call will be + /// ignored. /// /// This method can be chained within `provide` implementations to concisely /// provide multiple objects. - pub fn provide(self: Pin<&mut Self>, value: &'a T) -> Pin<&mut Self> { - self.provide_with(|| value) + /// + /// # Example + /// + /// ``` + /// # use object_provider::{ObjectProvider, Request}; + /// # use std::fmt; + /// struct MyProvider { + /// name: String, + /// } + /// + /// impl ObjectProvider for MyProvider { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request + /// .provide_ref::(&self) + /// .provide_ref::(&self.name) + /// .provide_ref::(&self.name) + /// .provide_ref::(&self.name); + /// } + /// } + /// ``` + pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { + self.provide_ref_with(|| value) } - /// Lazily provides an object of type `T` in response to this request. + /// Lazily provides a reference of type `&'a T` in response to this request. /// - /// If an object of type `T` has already been provided for this request, the - /// existing value will be replaced by the newly provided value. + /// If a reference of type `&'a T` has already been provided for this + /// request, or if the request is for a different type, this call will be + /// ignored. /// /// The passed closure is only called if the value will be successfully /// provided. /// /// This method can be chained within `provide` implementations to concisely /// provide multiple objects. - pub fn provide_with(mut self: Pin<&mut Self>, cb: F) -> Pin<&mut Self> + /// + /// # Example + /// + /// ``` + /// # use object_provider::{ObjectProvider, Request}; + /// # fn expensive_condition() -> bool { true } + /// struct MyProvider { + /// a: String, + /// b: String, + /// } + /// + /// impl ObjectProvider for MyProvider { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref_with::(|| { + /// if expensive_condition() { + /// &self.a + /// } else { + /// &self.b + /// } + /// }); + /// } + /// } + /// ``` + pub fn provide_ref_with(&mut self, cb: F) -> &mut Self where F: FnOnce() -> &'a T, { - if let Some(buf) = self.as_mut().downcast_buf::() { - // NOTE: We could've already provided a value here of type `T`, - // which will be clobbered in this case. - *buf = Some(cb()); + if let Some(RefResponse(response @ None)) = self.downcast_ref_response::() { + *response = Some(cb()); } self } - /// Returns `true` if the requested type is the same as `T` - pub fn is(&self) -> bool { - self.type_id == TypeId::of::() + /// Provides an value of type `T` in response to this request. + /// + /// If a value of type `T` has already been provided for this request, or if + /// the request is for a different type, this call will be ignored. + /// + /// This method can be chained within `provide` implementations to concisely + /// provide multiple objects. + /// + /// # Example + /// + /// ``` + /// # use object_provider::{ObjectProvider, Request}; + /// struct MyProvider { + /// count: u32, + /// } + /// + /// impl ObjectProvider for MyProvider { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request + /// .provide_value::(self.count) + /// .provide_value::<&'static str>("hello, world!"); + /// } + /// } + /// ``` + pub fn provide_value(&mut self, value: T) -> &mut Self { + self.provide_value_with(|| value) } - /// Try to downcast this `Request` into a reference to the typed - /// `RequestBuf` object, and access the trailing `Option<&'a T>`. - /// - /// This method will return `None` if `self` is not the prefix of a - /// `RequestBuf<'_, T>`. - fn downcast_buf(self: Pin<&mut Self>) -> Option<&mut Option<&'a T>> { - if self.is::() { - // Safety: `self` is pinned, meaning it exists as the first - // field within our `RequestBuf`. As the type matches, and - // `RequestBuf` has a known in-memory layout, this downcast is - // sound. - unsafe { - let ptr = self.get_unchecked_mut() as *mut Self as *mut RequestBuf<'a, T>; - Some(&mut (*ptr).value) - } - } else { - None + /// Lazily provides a value of type `T` in response to this request. + /// + /// If a value of type `T` has already been provided for this request, or if + /// the request is for a different type, this call will be ignored. + /// + /// The passed closure is only called if the value will be successfully + /// provided. + /// + /// This method can be chained within `provide` implementations to concisely + /// provide multiple objects. + /// + /// # Example + /// + /// ``` + /// # use object_provider::{ObjectProvider, Request}; + /// struct MyProvider { + /// count: u32, + /// } + /// + /// impl ObjectProvider for MyProvider { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request + /// .provide_value_with::(|| self.count / 10) + /// .provide_value_with::(|| format!("{}", self.count)); + /// } + /// } + /// ``` + pub fn provide_value_with(&mut self, cb: F) -> &mut Self + where + F: FnOnce() -> T, + { + if let Some(ValueResponse(response @ None)) = self.downcast_value_response::() { + *response = Some(cb()); } + self } - /// Calls the provided closure with a request for the the type `T`, returning - /// `Some(&T)` if the request was fulfilled, and `None` otherwise. - pub fn with(f: F) -> Option<&'a T> + /// Returns `true` if the requested type is `&'a T` + pub fn is_ref(&self) -> bool { + self.type_id == TypeId::of::>() + } + + /// Returns `true` if the requested type is `T` + pub fn is_value(&self) -> bool { + self.type_id == TypeId::of::>() + } + + /// Calls the provided closure with a request for the the type `&'a T`, + /// returning `Some(&T)` if the request was fulfilled, and `None` otherwise. + /// + /// The `ObjectProviderExt` trait provides helper methods specifically for + /// types implementing `ObjectProvider`. + /// + /// # Example + /// + /// ``` + /// # use object_provider::Request; + /// let response: Option<&str> = Request::request_ref(|request| { + /// // ... + /// request.provide_ref::("hello, world"); + /// }); + /// assert_eq!(response, Some("hello, world")); + /// ``` + pub fn request_ref(cb: F) -> Option<&'a T> where - F: FnOnce(Pin<&mut Self>), + F: FnOnce(&mut Request<'a>), { - let mut buf = RequestBuf::new(); - // safety: We never move `buf` after creating `pinned`. - let mut pinned = unsafe { Pin::new_unchecked(&mut buf) }; - f(pinned.as_mut().request()); - pinned.take() + let mut request = Request::new_ref(); + cb(&mut request); + request.response.0 } -} -impl<'a> fmt::Debug for Request<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Request") - .field("type_id", &self.type_id) - .finish() + /// Calls the provided closure with a request for the the type `T`, + /// returning `Some(T)` if the request was fulfilled, and `None` otherwise. + /// + /// The `ObjectProviderExt` trait provides helper methods specifically for + /// types implementing `ObjectProvider`. + /// + /// # Example + /// + /// ``` + /// # use object_provider::Request; + /// let response: Option = Request::request_value(|request| { + /// // ... + /// request.provide_value::(5); + /// }); + /// assert_eq!(response, Some(5)); + /// ``` + pub fn request_value(cb: F) -> Option + where + F: FnOnce(&mut Request<'a>), + { + let mut request = Request::new_value(); + cb(&mut request); + request.response.0 } } -// Needs to have a known layout so we can do unsafe pointer shenanigans. -#[repr(C)] -struct RequestBuf<'a, T: ?Sized + 'static> { - request: Request<'a>, - value: Option<&'a T>, +impl<'a, T: ?Sized + 'static> Request<'a, RefResponse<'a, T>> { + /// Create a new reference request object. + /// + /// The returned value will unsize to `Request<'a>`, and can be passed to + /// functions accepting it as an argument to request `&'a T` references. + fn new_ref() -> Self { + // safety: Initializes `type_id` to `RefResponse<'static, T>`, which + // corresponds to the response type `RefResponse<'a, T>`. + Request { + marker: PhantomData, + type_id: TypeId::of::>(), + response: RefResponse(None), + } + } } -impl<'a, T: ?Sized + 'static> RequestBuf<'a, T> { - fn new() -> Self { - RequestBuf { - request: Request { - type_id: TypeId::of::(), - _pinned: PhantomPinned, - _marker: PhantomData, - }, - value: None, +impl Request<'_, ValueResponse> { + /// Create a new value request object. + /// + /// The returned value will unsize to `Request<'a>`, and can be passed to + /// functions accepting it as an argument to request `T` values. + fn new_value() -> Self { + // safety: Initializes `type_id` to `ValueResponse`, which + // corresponds to the response type `ValueResponse`. + Request { + marker: PhantomData, + type_id: TypeId::of::>(), + response: ValueResponse(None), } } +} + +/// Trait to provide other objects based on a requested type at runtime. +/// +/// See also the [`ObjectProviderExt`] trait which provides the `request_ref` and +/// `request_value` methods. +pub trait ObjectProvider { + /// Provide an object in response to `request`. + fn provide<'a>(&'a self, request: &mut Request<'a>); +} - fn request(self: Pin<&mut Self>) -> Pin<&mut Request<'a>> { - // safety: projecting Pin onto our `request` field. - unsafe { self.map_unchecked_mut(|this| &mut this.request) } +/// Methods supported by all [`ObjectProvider`] implementors. +pub trait ObjectProviderExt { + /// Request a reference of type `&T` from an object provider. + fn request_ref(&self) -> Option<&T>; + + /// Request an owned value of type `T` from an object provider. + fn request_value(&self) -> Option; +} + +impl ObjectProviderExt for O { + fn request_ref(&self) -> Option<&T> { + Request::request_ref(|request| self.provide(request)) } - fn take(self: Pin<&mut Self>) -> Option<&'a T> { - // safety: `Option<&'a T>` is `Unpin` - unsafe { self.get_unchecked_mut().value.take() } + fn request_value(&self) -> Option { + Request::request_value(|request| self.provide(request)) } } + ``` ### Define a generic accessor on the `Error` trait @@ -433,7 +621,7 @@ impl<'a, T: ?Sized + 'static> RequestBuf<'a, T> { pub trait Error { // ... - fn provide_context<'a>(&'a self, _request: Pin<&mut Request<'a>>) {} + fn provide_context<'a>(&'a self, _request: &mut Request<'a>) {} } ``` @@ -441,8 +629,12 @@ pub trait Error { ```rust impl dyn Error { - pub fn context(&self) -> Option<&T> { - Request::with::(|req| self.provide_context(req)) + pub fn context_ref(&self) -> Option<&T> { + Request::request_ref(|req| self.provide_context(req)) + } + + pub fn context(&self) -> Option { + Request::request_value(|req| self.provide_context(req)) } } ``` @@ -451,8 +643,10 @@ impl dyn Error { [drawbacks]: #drawbacks * The `Request` api is being added purely to help with this function. This will - likely need some design work to make it more generally applicable, hopefully - as a struct in `core::any`. + likely need some design work to make it more generally applicable, + hopefully as a struct in `core::any`. **Update**: this API might also be + useful for `std::task::Context` to help pass data to executors in a backend + agnostic way. * The `context` function name is currently widely used throughout the rust error handling ecosystem in libraries like `anyhow` and `snafu` as an ergonomic version of `map_err`. If we settle on `context` as the final name @@ -526,12 +720,14 @@ previous additions to the `Error` trait. * `context`/`context_ref`/`provide_context`/`provide_context`/`request_context` * `member`/`member_ref` * `provide`/`request` -* Should there be a by-value version for accessing temporaries? - * We bring this up specifically for the case where you want to use this +* ~~Should there be a by-value version for accessing temporaries?~~ **Update**: + The object provider API in this RFC has been updated to include a by-value + variant for passing out owned data. + * ~~We bring this up specifically for the case where you want to use this function to get an `Option<&[&dyn Error]>` out of an error, in this case, it is unlikely that the error behind the trait object is actually storing the errors as `dyn Error`s, and theres no easy way to allocate storage to - store the trait objects. + store the trait objects.~~ # Future possibilities [future-possibilities]: #future-possibilities @@ -543,7 +739,7 @@ return trace could be built up with: ```rust let mut locations = e .chain() - .filter_map(|e| e.context::<[&'static Location<'static>]>()) + .filter_map(|e| e.context_ref::<[&'static Location<'static>]>()) .flat_map(|locs| locs.iter()); ``` From 84c8bf73e874c0f8481477383559e19647e78c72 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Fri, 4 Dec 2020 10:25:40 -0800 Subject: [PATCH 38/40] add examples for by value --- text/0000-dyn-error-generic-member-access.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 620a8905fe1..e4995e118a5 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -87,9 +87,14 @@ fn provide_context<'a>(&'a self, mut request: &mut Request<'a>) { request .provide_ref::(&self.backtrace) .provide_ref::(&self.span_trace) + // supports dynamically sized types .provide_ref::(&self.source) .provide_ref::>>(&self.locations) - .provide_ref::<[&'static Location<'static>]>(&self.locations); + .provide_ref::<[&'static Location<'static>]>(&self.locations) + // can be used to upcast self to other trait objects + .provide_ref::(&self) + // or to pass owned values + .provide_value::(self.exit_code); } ``` From fb02f91c2bb08f964845bc7c354a567e862ea16f Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Fri, 26 Feb 2021 14:47:18 -0800 Subject: [PATCH 39/40] Apply suggestions from code review Co-authored-by: bstrie <865233+bstrie@users.noreply.github.com> --- text/0000-dyn-error-generic-member-access.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index e4995e118a5..1e3fd140045 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -198,7 +198,7 @@ struct ExampleError { impl fmt::Display for ExampleError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "Failed to read instrs from {}", path.display()) + write!(fmt, "Failed to read instrs from {}", self.path.display()) } } @@ -737,7 +737,7 @@ previous additions to the `Error` trait. # Future possibilities [future-possibilities]: #future-possibilities -This opens the door to supporting `Error Return Traces`, similar to zigs, where +This opens the door to supporting [`Error Return Traces`](https://ziglang.org/documentation/master/#toc-Error-Return-Traces), similar to zigs, where if each return location is stored in a `Vec<&'static Location<'static>>` a full return trace could be built up with: From 61be66be116d39e1c47120645cf59237798fb5f5 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 12 Apr 2021 14:38:52 -0700 Subject: [PATCH 40/40] update RFC to be based on dyno design --- text/0000-dyn-error-generic-member-access.md | 502 +++++++++---------- 1 file changed, 227 insertions(+), 275 deletions(-) diff --git a/text/0000-dyn-error-generic-member-access.md b/text/0000-dyn-error-generic-member-access.md index 1e3fd140045..94ffdd0198a 100644 --- a/text/0000-dyn-error-generic-member-access.md +++ b/text/0000-dyn-error-generic-member-access.md @@ -290,334 +290,286 @@ role as `&dyn Any` except that it supports other trait objects as the requested type. Here is the implementation for the proof of concept, based on Nika Layzell's -[object-provider crate]: +[dyno crate]: A usable version of this is available in the [proof of concept] repo under `fakecore/src/any.rs`. ```rust use core::any::TypeId; -use core::marker::PhantomData; -mod private { - pub trait Response<'a>: 'a {} -} +#[cfg(feature = "alloc")] +extern crate alloc; -/// A response to a ref request. -struct RefResponse<'a, T: ?Sized + 'static>(Option<&'a T>); -impl<'a, T: ?Sized + 'static> private::Response<'a> for RefResponse<'a, T> {} +#[cfg(feature = "alloc")] +use alloc::boxed::Box; -/// A response to a value request. -struct ValueResponse(Option); -impl<'a, T: 'static> private::Response<'a> for ValueResponse {} +pub mod provider { + //! Tag-based value lookup API for trait objects. + //! + //! This provides a similar API to my `object_provider` crate, built on top of + //! `dyno` -/// A dynamic request for an object based on its type. -pub struct Request<'a, R = dyn private::Response<'a>> -where - R: ?Sized + private::Response<'a>, -{ - marker: PhantomData<&'a ()>, + use super::{Tag, Tagged}; - /// A `TypeId` marker for the type stored in `R`. + /// An untyped request for a value of a specific type. /// - /// Will be the TypeId of either `RefResponse<'static, T>` or - /// `ValueResponse`. - type_id: TypeId, + /// This type is generally used as an `&mut Request<'a>` outparameter. + #[repr(transparent)] + pub struct Request<'a> { + tagged: dyn Tagged<'a> + 'a, + } - /// A type erased `RefResponse` or `ValueResponse` containing the response - /// value. - response: R, -} + impl<'a> Request<'a> { + /// Helper for performing transmutes as `Request<'a>` has the same layout as + /// `dyn Tagged<'a> + 'a`, just with a different type! + /// + /// This is just to have our own methods on it, and less of the interface + /// exposed on the `provide` implementation. + fn wrap_tagged<'b>(t: &'b mut (dyn Tagged<'a> + 'a)) -> &'b mut Self { + unsafe { &mut *(t as *mut (dyn Tagged<'a> + 'a) as *mut Request<'a>) } + } -impl<'a> Request<'a> { - /// Perform a checked downcast of `response` to `Option<&'a T>` - fn downcast_ref_response<'b, T: ?Sized + 'static>( - &'b mut self, - ) -> Option<&'b mut RefResponse<'a, T>> { - if self.is_ref::() { - // safety: If `self.is_ref::()` returns true, `response` must be - // of the correct type. This is enforced by the private `type_id` - // field. - Some(unsafe { &mut *(&mut self.response as *mut _ as *mut RefResponse<'a, T>) }) - } else { - None + pub fn is(&self) -> bool + where + I: Tag<'a>, + { + self.tagged.is::>() } - } - /// Perform a checked downcast of `response` to `Option` - fn downcast_value_response<'b, T: 'static>(&'b mut self) -> Option<&'b mut ValueResponse> { - if self.is_value::() { - // safety: If `self.is_value::()` returns true, `response` must - // be of the correct type. This is enforced by the private `type_id` - // field. - Some(unsafe { &mut *(&mut self.response as *mut _ as *mut ValueResponse) }) - } else { - None + pub fn provide(&mut self, value: I::Type) -> &mut Self + where + I: Tag<'a>, + { + if let Some(res @ None) = self.tagged.downcast_mut::>() { + *res = Some(value); + } + self + } + + pub fn provide_ref(&mut self, value: &'a I) -> &mut Self + { + use crate::any::tag::Ref; + if let Some(res @ None) = self.tagged.downcast_mut::>>() { + *res = Some(value); + } + self + } + + pub fn provide_with(&mut self, f: F) -> &mut Self + where + I: Tag<'a>, + F: FnOnce() -> I::Type, + { + if let Some(res @ None) = self.tagged.downcast_mut::>() { + *res = Some(f()); + } + self } } - /// Provides a reference of type `&'a T` in response to this request. - /// - /// If a reference of type `&'a T` has already been provided for this - /// request, or if the request is for a different type, this call will be - /// ignored. - /// - /// This method can be chained within `provide` implementations to concisely - /// provide multiple objects. - /// - /// # Example - /// - /// ``` - /// # use object_provider::{ObjectProvider, Request}; - /// # use std::fmt; - /// struct MyProvider { - /// name: String, - /// } - /// - /// impl ObjectProvider for MyProvider { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request - /// .provide_ref::(&self) - /// .provide_ref::(&self.name) - /// .provide_ref::(&self.name) - /// .provide_ref::(&self.name); - /// } - /// } - /// ``` - pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { - self.provide_ref_with(|| value) + pub trait Provider { + fn provide<'a>(&'a self, request: &mut Request<'a>); } - /// Lazily provides a reference of type `&'a T` in response to this request. - /// - /// If a reference of type `&'a T` has already been provided for this - /// request, or if the request is for a different type, this call will be - /// ignored. - /// - /// The passed closure is only called if the value will be successfully - /// provided. - /// - /// This method can be chained within `provide` implementations to concisely - /// provide multiple objects. - /// - /// # Example - /// - /// ``` - /// # use object_provider::{ObjectProvider, Request}; - /// # fn expensive_condition() -> bool { true } - /// struct MyProvider { - /// a: String, - /// b: String, - /// } - /// - /// impl ObjectProvider for MyProvider { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request.provide_ref_with::(|| { - /// if expensive_condition() { - /// &self.a - /// } else { - /// &self.b - /// } - /// }); - /// } - /// } - /// ``` - pub fn provide_ref_with(&mut self, cb: F) -> &mut Self + impl dyn Provider { + pub fn request<'a, I>(&'a self) -> Option + where + I: Tag<'a>, + { + request::(|request| self.provide(request)) + } + } + + pub fn request<'a, I, F>(f: F) -> Option<>::Type> where - F: FnOnce() -> &'a T, + I: Tag<'a>, + F: FnOnce(&mut Request<'a>), { - if let Some(RefResponse(response @ None)) = self.downcast_ref_response::() { - *response = Some(cb()); - } - self + let mut result: Option<>::Type> = None; + f(Request::<'a>::wrap_tagged(::tag_mut::>( + &mut result, + ))); + result } - /// Provides an value of type `T` in response to this request. - /// - /// If a value of type `T` has already been provided for this request, or if - /// the request is for a different type, this call will be ignored. - /// - /// This method can be chained within `provide` implementations to concisely - /// provide multiple objects. - /// - /// # Example - /// - /// ``` - /// # use object_provider::{ObjectProvider, Request}; - /// struct MyProvider { - /// count: u32, - /// } + /// Implementation detail: Specific `Tag` tag used by the `Request` code under + /// the hood. /// - /// impl ObjectProvider for MyProvider { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request - /// .provide_value::(self.count) - /// .provide_value::<&'static str>("hello, world!"); - /// } - /// } - /// ``` - pub fn provide_value(&mut self, value: T) -> &mut Self { - self.provide_value_with(|| value) + /// Composition of `Tag` types! + struct ReqTag(I); + impl<'a, I: Tag<'a>> Tag<'a> for ReqTag { + type Type = Option; } +} - /// Lazily provides a value of type `T` in response to this request. - /// - /// If a value of type `T` has already been provided for this request, or if - /// the request is for a different type, this call will be ignored. - /// - /// The passed closure is only called if the value will be successfully - /// provided. - /// - /// This method can be chained within `provide` implementations to concisely - /// provide multiple objects. - /// - /// # Example - /// - /// ``` - /// # use object_provider::{ObjectProvider, Request}; - /// struct MyProvider { - /// count: u32, - /// } - /// - /// impl ObjectProvider for MyProvider { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request - /// .provide_value_with::(|| self.count / 10) - /// .provide_value_with::(|| format!("{}", self.count)); - /// } - /// } - /// ``` - pub fn provide_value_with(&mut self, cb: F) -> &mut Self - where - F: FnOnce() -> T, - { - if let Some(ValueResponse(response @ None)) = self.downcast_value_response::() { - *response = Some(cb()); - } - self +pub mod tag { + //! Simple type-based tag values for use in generic code. + use super::Tag; + use core::marker::PhantomData; + + /// Type-based `Tag` for `&'a T` types. + pub struct Ref(PhantomData); + + impl<'a, T: ?Sized + 'static> Tag<'a> for Ref { + type Type = &'a T; } - /// Returns `true` if the requested type is `&'a T` - pub fn is_ref(&self) -> bool { - self.type_id == TypeId::of::>() + /// Type-based `Tag` for `&'a mut T` types. + pub struct RefMut(PhantomData); + + impl<'a, T: ?Sized + 'static> Tag<'a> for RefMut { + type Type = &'a mut T; } - /// Returns `true` if the requested type is `T` - pub fn is_value(&self) -> bool { - self.type_id == TypeId::of::>() + /// Type-based `Tag` for concrete types. + pub struct Value(PhantomData); + + impl Tag<'_> for Value { + type Type = T; } +} - /// Calls the provided closure with a request for the the type `&'a T`, - /// returning `Some(&T)` if the request was fulfilled, and `None` otherwise. - /// - /// The `ObjectProviderExt` trait provides helper methods specifically for - /// types implementing `ObjectProvider`. - /// - /// # Example +/// An identifier which may be used to tag a specific +pub trait Tag<'a>: Sized + 'static { + /// The type of values which may be tagged by this `Tag`. + type Type: 'a; +} + +mod private { + pub trait Sealed {} +} + +/// Sealed trait representing a type-erased tagged object. +pub unsafe trait Tagged<'a>: private::Sealed + 'a { + /// The `TypeId` of the `Tag` this value was tagged with. + fn tag_id(&self) -> TypeId; +} + +/// Internal wrapper type with the same representation as a known external type. +#[repr(transparent)] +struct TaggedImpl<'a, I> +where + I: Tag<'a>, +{ + _value: I::Type, +} + +impl<'a, I> private::Sealed for TaggedImpl<'a, I> where I: Tag<'a> {} + +unsafe impl<'a, I> Tagged<'a> for TaggedImpl<'a, I> +where + I: Tag<'a>, +{ + fn tag_id(&self) -> TypeId { + TypeId::of::() + } +} + +// FIXME: This should also handle the cases for `dyn Tagged<'a> + Send`, +// `dyn Tagged<'a> + Send + Sync` and `dyn Tagged<'a> + Sync`... +// +// Should be easy enough to do it with a macro... +impl<'a> dyn Tagged<'a> { + /// Tag a reference to a concrete type with a given `Tag`. /// - /// ``` - /// # use object_provider::Request; - /// let response: Option<&str> = Request::request_ref(|request| { - /// // ... - /// request.provide_ref::("hello, world"); - /// }); - /// assert_eq!(response, Some("hello, world")); - /// ``` - pub fn request_ref(cb: F) -> Option<&'a T> + /// This is like an unsizing coercion, but must be performed explicitly to + /// specify the specific tag. + pub fn tag_ref(value: &I::Type) -> &dyn Tagged<'a> where - F: FnOnce(&mut Request<'a>), + I: Tag<'a>, { - let mut request = Request::new_ref(); - cb(&mut request); - request.response.0 + // SAFETY: `TaggedImpl<'a, I>` has the same representation as `I::Type` + // due to `#[repr(transparent)]`. + unsafe { &*(value as *const I::Type as *const TaggedImpl<'a, I>) } } - /// Calls the provided closure with a request for the the type `T`, - /// returning `Some(T)` if the request was fulfilled, and `None` otherwise. - /// - /// The `ObjectProviderExt` trait provides helper methods specifically for - /// types implementing `ObjectProvider`. - /// - /// # Example + /// Tag a reference to a concrete type with a given `Tag`. /// - /// ``` - /// # use object_provider::Request; - /// let response: Option = Request::request_value(|request| { - /// // ... - /// request.provide_value::(5); - /// }); - /// assert_eq!(response, Some(5)); - /// ``` - pub fn request_value(cb: F) -> Option + /// This is like an unsizing coercion, but must be performed explicitly to + /// specify the specific tag. + pub fn tag_mut(value: &mut I::Type) -> &mut dyn Tagged<'a> where - F: FnOnce(&mut Request<'a>), + I: Tag<'a>, { - let mut request = Request::new_value(); - cb(&mut request); - request.response.0 + // SAFETY: `TaggedImpl<'a, I>` has the same representation as `I::Type` + // due to `#[repr(transparent)]`. + unsafe { &mut *(value as *mut I::Type as *mut TaggedImpl<'a, I>) } } -} -impl<'a, T: ?Sized + 'static> Request<'a, RefResponse<'a, T>> { - /// Create a new reference request object. + /// Tag a Box of a concrete type with a given `Tag`. /// - /// The returned value will unsize to `Request<'a>`, and can be passed to - /// functions accepting it as an argument to request `&'a T` references. - fn new_ref() -> Self { - // safety: Initializes `type_id` to `RefResponse<'static, T>`, which - // corresponds to the response type `RefResponse<'a, T>`. - Request { - marker: PhantomData, - type_id: TypeId::of::>(), - response: RefResponse(None), - } + /// This is like an unsizing coercion, but must be performed explicitly to + /// specify the specific tag. + #[cfg(feature = "alloc")] + pub fn tag_box(value: Box) -> Box> + where + I: Tag<'a>, + { + // SAFETY: `TaggedImpl<'a, I>` has the same representation as `I::Type` + // due to `#[repr(transparent)]`. + unsafe { Box::from_raw(Box::into_raw(value) as *mut TaggedImpl<'a, I>) } } -} -impl Request<'_, ValueResponse> { - /// Create a new value request object. - /// - /// The returned value will unsize to `Request<'a>`, and can be passed to - /// functions accepting it as an argument to request `T` values. - fn new_value() -> Self { - // safety: Initializes `type_id` to `ValueResponse`, which - // corresponds to the response type `ValueResponse`. - Request { - marker: PhantomData, - type_id: TypeId::of::>(), - response: ValueResponse(None), - } + /// Returns `true` if the dynamic type is tagged with `I`. + #[inline] + pub fn is(&self) -> bool + where + I: Tag<'a>, + { + self.tag_id() == TypeId::of::() } -} -/// Trait to provide other objects based on a requested type at runtime. -/// -/// See also the [`ObjectProviderExt`] trait which provides the `request_ref` and -/// `request_value` methods. -pub trait ObjectProvider { - /// Provide an object in response to `request`. - fn provide<'a>(&'a self, request: &mut Request<'a>); -} - -/// Methods supported by all [`ObjectProvider`] implementors. -pub trait ObjectProviderExt { - /// Request a reference of type `&T` from an object provider. - fn request_ref(&self) -> Option<&T>; - - /// Request an owned value of type `T` from an object provider. - fn request_value(&self) -> Option; -} + /// Returns some reference to the dynamic value if it is tagged with `I`, + /// or `None` if it isn't. + #[inline] + pub fn downcast_ref(&self) -> Option<&I::Type> + where + I: Tag<'a>, + { + if self.is::() { + // SAFETY: Just checked whether we're pointing to a + // `TaggedImpl<'a, I>`, which was cast to from an `I::Type`. + unsafe { Some(&*(self as *const dyn Tagged<'a> as *const I::Type)) } + } else { + None + } + } -impl ObjectProviderExt for O { - fn request_ref(&self) -> Option<&T> { - Request::request_ref(|request| self.provide(request)) + /// Returns some reference to the dynamic value if it is tagged with `I`, + /// or `None` if it isn't. + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut I::Type> + where + I: Tag<'a>, + { + if self.is::() { + // SAFETY: Just checked whether we're pointing to a + // `TaggedImpl<'a, I>`, which was cast to from an `I::Type`. + unsafe { Some(&mut *(self as *mut dyn Tagged<'a> as *mut I::Type)) } + } else { + None + } } - fn request_value(&self) -> Option { - Request::request_value(|request| self.provide(request)) + #[inline] + #[cfg(feature = "alloc")] + pub fn downcast_box(self: Box) -> Result, Box> + where + I: Tag<'a>, + { + if self.is::() { + unsafe { + // SAFETY: Just checked whether we're pointing to a + // `TaggedImpl<'a, I>`, which was cast to from an `I::Type`. + let raw: *mut dyn Tagged<'a> = Box::into_raw(self); + Ok(Box::from_raw(raw as *mut I::Type)) + } + } else { + Err(self) + } } } - ``` ### Define a generic accessor on the `Error` trait @@ -751,5 +703,5 @@ let mut locations = e [`SpanTrace`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.SpanTrace.html [`Request`]: https://github.com/yaahc/nostd-error-poc/blob/master/fakecore/src/any.rs [alternative proposal]: #use-an-alternative-proposal-that-relies-on-the-any-trait-for-downcasting -[object-provider crate]: https://github.com/mystor/object-provider +[dyno crate]: https://github.com/mystor/dyno [proof of concept]: https://github.com/yaahc/nostd-error-poc