Repository: komora-io/terrors Branch: main Commit: 8f3b237a32d5 Files: 10 Total size: 58.5 KB Directory structure: gitextract_i6pk_blr/ ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src/ │ ├── lib.rs │ ├── one_of.rs │ ├── one_of_to_enum.rs │ └── type_set.rs └── tests/ └── usability.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /target Cargo.lock ================================================ FILE: Cargo.toml ================================================ [package] name = "terrors" version = "0.3.3" edition = "2021" authors = ["Tyler Neely "] documentation = "https://docs.rs/terrors/" description = "ergonomic and precise error handling built atop type-level set arithmetic" license = "MIT OR Apache-2.0" repository = "https://github.com/komora-io/terrors" categories = ["rust-patterns"] keywords = ["error", "error-handling", "type-level", "anonymous", "sum"] readme = "README.md" [features] error_provide = [] error_provide_feature = [] ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2024 Tyler Neely Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2024 Tyler Neely Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # terrors - the Rust error **handling** library Handling errors means taking a set of possible error types, removing the ones that are locally addressible, and then if the set of errors is not within those local concerns, propagating the remainder to a caller. The caller should not receive the local errors of the callee. # Principles * Error types should be precise. * `terrors::OneOf` solves this by making precise sets of possible errors: * low friction to specify * low friction to narrow by specific error handlers * low friction to broaden to pass up the stack * Error handling should follow the single responsibility principle * if every error in a system is spread everywhere else, there is no clear responsibility for where it needs to be handled. * No macros. * Users should not have to learn some new DSL for error handling that every macro entails. # Examples ```rust use terrors::OneOf; let one_of_3: OneOf<(String, u32, Vec)> = OneOf::new(5); let narrowed_res: Result)>> = one_of_3.narrow(); assert_eq!(5, narrowed_res.unwrap()); ``` OneOf can also be broadened to a superset, checked at compile-time. ```rust use terrors::OneOf; struct Timeout; struct AllocationFailure; struct RetriesExhausted; fn allocate_box() -> Result, OneOf<(AllocationFailure,)>> { Err(AllocationFailure.into()) } fn send() -> Result<(), OneOf<(Timeout,)>> { Err(Timeout.into()) } fn allocate_and_send() -> Result<(), OneOf<(AllocationFailure, Timeout)>> { let boxed_byte: Box = allocate_box().map_err(OneOf::broaden)?; send().map_err(OneOf::broaden)?; Ok(()) } fn retry() -> Result<(), OneOf<(AllocationFailure, RetriesExhausted)>> { for _ in 0..3 { let Err(err) = allocate_and_send() else { return Ok(()); }; // keep retrying if we have a Timeout, // but punt allocation issues to caller. match err.narrow::() { Ok(_timeout) => {}, Err(one_of_others) => return Err(one_of_others.broaden()), } } Err(OneOf::new(RetriesExhausted)) } ``` `OneOf` also implements `Clone`, `Debug`, `Display`, `Send`, `Sync` and/or `std::error::Error` if all types in the type set do as well: ```rust use std::error::Error; use std::io; use terrors::OneOf; let o_1: OneOf<(u32, String)> = OneOf::new(5_u32); // Debug is implemented if all types in the type set implement Debug dbg!(&o_1); // Display is implemented if all types in the type set implement Display println!("{}", o_1); let cloned = o_1.clone(); type E = io::Error; let e = io::Error::new(io::ErrorKind::Other, "wuaaaaahhhzzaaaaaaaa"); let o_2: OneOf<(E,)> = OneOf::new(e); // std::error::Error is implemented if all types in the type set implement it dbg!(o_2.description()); ``` OneOf can also be turned into an owned or referenced enum form: ```rust use terrors::{OneOf, E2}; let o_1: OneOf<(u32, String)> = OneOf::new(5_u32); match o_1.as_enum() { E2::A(u) => { println!("handling reference {u}: u32") } E2::B(s) => { println!("handling reference {s}: String") } } match o_1.to_enum() { E2::A(u) => { println!("handling owned {u}: u32") } E2::B(s) => { println!("handling owned {s}: String") } } ``` ### Motivation The paper [Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-intensive Systems](https://www.eecg.toronto.edu/~yuan/papers/failure_analysis_osdi14.pdf) is goldmine of fascinating statistics that illuminate the software patterns that tend to correspond to system failures. This is one of my favorites: ```no_compile almost all (92%) of the catastrophic system failures are the result of incorrect handling of non-fatal errors explicitly signaled in software. ``` Our systems are falling over because we aren't handling our errors. We're doing fine when it comes to signalling their existence, but we need to actually handle them. When we write Rust, we tend to encounter a variety of different error types. Sometimes we need to put multiple possible errors into a container that is then returned from a function, where the caller or a transitive caller is expected to handle the specific problem that arose. As we grow a codebase, more of these situations pop up. While it's not so much effort to write custom enums in one or two places that hold the precise set of possible errors, most people resort to one of two strategies for minimizing the effort that goes into propagating their error types: * A large top-level enum that holds variants for errors originating across the codebase, tending to grow larger and larger over time, undermining the ability to use exhaustive pattern matching to confidently ensure that local concerns are not bubbling up the stack. * A boxed trait that is easy to convert errors into, but then hides information about what may actually be inside. You don't know where it's been or where it's going. As the number of different source error types that these error containers hold increases, the amount of information that the container communicates to people who encounter it decreases. It becomes increasingly unclear what the error container actually holds. As the precision of the type goes down, so does a human's ability to reason about where the appropriate place is to handle any particular concern within it. We have to increase the precision in our error types. People don't write a precise enum for every function that may only return some subset of errors because we would end up with a ton of small enum types that only get used in one or two places. This is the pain that drives people to using overly-broad error enums or overly-smooth boxed dynamic error traits, reducing their ability to handle their errors. ### Cool stuff This crate is built around `OneOf`, which functions as a form of anonymous enum that can be narrowed in ways that may be familiar for users of TypeScript etc... Our error containers need to get smaller as individual errors are peeled off and handled, leaving the reduced remainder of possible error types if the local concerns are not present. The cool thing about it is that it is built on top of a type-level heterogenous set of possible error types, where there's only one actual value among the different possibilities. Rather than having a giant ball of mud enum or boxed trait object that is never clear what it actually contains, causing you to never handle individual concerns from, the idea of this is that you can have a minimized set of actual error types that may thread through the stack. The nice thing about this type-level set of possibilities is that any specific type can be peeled off while narrowing the rest of the types if the narrowing fails. Both narrowing and broadening are based on compile-time error type set checking. ### The Trade-Off Type-level programming is something that I have tried hard to avoid for most of my career due to confusing error messages resulting from compilation errors. These complex type checking failures produce errors that are challenging to reason about, and can often take several minutes to understand. I have tried hard to avoid exposing users of `terrors` to too many of the sharp edges in the underlying type machinery, but it is likely that if the source and destination type sets do not satisfy the `SupersetOf` trait in the right direction depending on whether `narrow` or `broaden` is being called, that the error will not be particularly pleasant to read. Just know that errors pretty much always mean that the superset relationship does not hold as required. Going forward, I believe most of the required traits can be implemented in ways that expose users to errors that look more like `(A, B) does not implement SupersetOf<(C, D), _>` instead of `Cons> does not implement SupersetOf>>` by leaning into the bidirectional type mapping that exists between the heterogenous type set `Cons` chains and more human-friendly type tuples. ### Special Thanks Much of the fancy type-level logic for reasoning about sets of error types was directly inspired by [frunk](https://docs.rs/frunk/latest/frunk/). I had been wondering for years about the feasibility of a data structure like `OneOf`, and had often assumed it was impossible, until I finally had an extended weekend to give it a deep dive. After many false starts, I finally came across [an article](https://archive.is/YwDMX) written by [lloydmeta](https://github.com/lloydmeta) (the author of frunk) about how frunk handles several related concerns in the context of a heterogenous list structure. Despite having used Rust for over 10 years, that article taught me a huge amount about how the language's type system can be used in interesting ways that addressed very practical needs. In particular, the general perspective in that blog post about how you can implement traits in a recursive way that is familiar from other functional languages was the missing primitive for working with Rust that I had not realized was possible for my first decade with the language. Thank you very much for creating frunk and telling the world about how you did it! ================================================ FILE: src/lib.rs ================================================ #![cfg_attr( feature = "error_provide_feature", feature(error_generic_member_access) )] #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] #[cfg(doctest)] pub struct ReadmeDoctests; mod one_of; mod one_of_to_enum; mod type_set; /// Similar to anonymous unions / enums in languages that support type narrowing. pub use one_of::OneOf; pub use type_set::{TypeSet, E1, E2, E3, E4, E5, E6, E7, E8, E9}; /* ------------------------- Helpers ----------------------- */ /// The final element of a type-level Cons list. #[doc(hidden)] #[derive(Debug)] pub enum End {} impl std::error::Error for End {} /// A compile-time list of types, similar to other basic functional list structures. #[doc(hidden)] #[derive(Debug)] pub struct Cons(core::marker::PhantomData, Tail); #[doc(hidden)] #[derive(Debug)] pub struct Recurse(Tail); ================================================ FILE: src/one_of.rs ================================================ use core::any::Any; use core::fmt; use core::marker::PhantomData; use core::ops::Deref; use std::error::Error; use crate::type_set::{ CloneFold, Contains, DebugFold, DisplayFold, ErrorFold, IsFold, Narrow, SupersetOf, TupleForm, TypeSet, }; use crate::{Cons, End}; /* ------------------------- OneOf ----------------------- */ /// `OneOf` is an open sum type. It differs from an enum /// in that you do not need to define any actual new type /// in order to hold some specific combination of variants, /// but rather you simply describe the OneOf as holding /// one value out of several specific possibilities, /// defined by using a tuple of those possible variants /// as the generic parameter for the `OneOf`. /// /// For example, a `OneOf<(String, u32)>` contains either /// a `String` or a `u32`. The value over a simple `Result` /// or other traditional enum starts to become apparent in larger /// codebases where error handling needs to occur in /// different places for different errors. `OneOf` allows /// you to quickly specify a function's return value as /// involving a precise subset of errors that the caller /// can clearly reason about. pub struct OneOf { pub(crate) value: Box, _pd: PhantomData, } fn _send_sync_error_assert() { use std::io; fn is_send(_: &T) {} fn is_sync(_: &T) {} fn is_error(_: &T) {} let o: OneOf<(io::Error,)> = OneOf::new(io::Error::new(io::ErrorKind::Other, "yooo")); is_send(&o); is_sync(&o); is_error(&o); } unsafe impl Send for OneOf where T: TypeSet + Send {} unsafe impl Sync for OneOf where T: TypeSet + Sync {} impl Deref for OneOf<(T,)> where T: 'static, { type Target = T; fn deref(&self) -> &T { self.value.downcast_ref::().unwrap() } } impl From for OneOf<(T,)> where T: 'static, { fn from(t: T) -> OneOf<(T,)> { OneOf::new(t) } } impl Clone for OneOf where E: TypeSet, E::Variants: Clone + CloneFold, { fn clone(&self) -> Self { let value = E::Variants::clone_fold(&self.value); OneOf { value, _pd: PhantomData, } } } impl fmt::Debug for OneOf where E: TypeSet, E::Variants: fmt::Debug + DebugFold, { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { E::Variants::debug_fold(&self.value, formatter) } } impl fmt::Display for OneOf where E: TypeSet, E::Variants: fmt::Display + DisplayFold, { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { E::Variants::display_fold(&self.value, formatter) } } impl Error for OneOf where E: TypeSet, E::Variants: Error + DebugFold + DisplayFold + ErrorFold, { fn source(&self) -> Option<&(dyn Error + 'static)> { E::Variants::source_fold(&self.value) } } impl OneOf where E: TypeSet, { /// Create a new `OneOf`. pub fn new(t: T) -> OneOf where T: Any, E::Variants: Contains, { OneOf { value: Box::new(t), _pd: PhantomData, } } /// Attempt to downcast the `OneOf` into a specific type, and /// if that fails, return a `OneOf` which does not contain that /// type as one of its possible variants. pub fn narrow( self, ) -> Result< Target, OneOf<<>::Remainder as TupleForm>::Tuple>, > where Target: 'static, E::Variants: Narrow, { if self.value.is::() { Ok(*self.value.downcast::().unwrap()) } else { Err(OneOf { value: self.value, _pd: PhantomData, }) } } /// Turns the `OneOf` into a `OneOf` with a set of variants /// which is a superset of the current one. This may also be /// the same set of variants, but in a different order. pub fn broaden(self) -> OneOf where Other: TypeSet, Other::Variants: SupersetOf, { OneOf { value: self.value, _pd: PhantomData, } } /// Attempt to split a subset of variants out of the `OneOf`, /// returning the remainder of possible variants if the value /// does not have one of the `TargetList` types. pub fn subset( self, ) -> Result< OneOf, OneOf<<>::Remainder as TupleForm>::Tuple>, > where TargetList: TypeSet, E::Variants: IsFold + SupersetOf, { if E::Variants::is_fold(&self.value) { Ok(OneOf { value: self.value, _pd: PhantomData, }) } else { Err(OneOf { value: self.value, _pd: PhantomData, }) } } /// For a `OneOf` with a single variant, return /// the contained value. pub fn take(self) -> Target where Target: 'static, E: TypeSet>, { *self.value.downcast::().unwrap() } /// Convert the `OneOf` to an owned enum for /// use in pattern matching etc... pub fn to_enum(self) -> E::Enum where E::Enum: From, { E::Enum::from(self) } /// Borrow the enum as an enum for use in /// pattern matching etc... pub fn as_enum<'a>(&'a self) -> E::EnumRef<'a> where E::EnumRef<'a>: From<&'a Self>, { E::EnumRef::from(&self) } } ================================================ FILE: src/one_of_to_enum.rs ================================================ use super::{OneOf, E1, E2, E3, E4, E5, E6, E7, E8, E9}; /* ------------------------- Enum conversions ----------------------- */ impl From> for E1 where A: 'static, { fn from(one_of: OneOf<(A,)>) -> Self { E1::A(*one_of.value.downcast().unwrap()) } } impl<'a, A> From<&'a OneOf<(A,)>> for E1<&'a A> where A: 'static, { fn from(one_of: &'a OneOf<(A,)>) -> Self { E1::A(one_of.value.downcast_ref().unwrap()) } } impl From> for E2 where A: 'static, B: 'static, { fn from(one_of: OneOf<(A, B)>) -> Self { if one_of.value.is::() { E2::A(*one_of.value.downcast().unwrap()) } else { E2::B(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B> From<&'a OneOf<(A, B)>> for E2<&'a A, &'a B> where A: 'static, B: 'static, { fn from(one_of: &'a OneOf<(A, B)>) -> Self { if one_of.value.is::() { E2::A(one_of.value.downcast_ref().unwrap()) } else { E2::B(one_of.value.downcast_ref().unwrap()) } } } impl From> for E3 where A: 'static, B: 'static, C: 'static, { fn from(one_of: OneOf<(A, B, C)>) -> Self { if one_of.value.is::() { E3::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E3::B(*one_of.value.downcast().unwrap()) } else { E3::C(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C> From<&'a OneOf<(A, B, C)>> for E3<&'a A, &'a B, &'a C> where A: 'static, B: 'static, C: 'static, { fn from(one_of: &'a OneOf<(A, B, C)>) -> Self { if one_of.value.is::() { E3::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E3::B(one_of.value.downcast_ref().unwrap()) } else { E3::C(one_of.value.downcast_ref().unwrap()) } } } impl From> for E4 where A: 'static, B: 'static, C: 'static, D: 'static, { fn from(one_of: OneOf<(A, B, C, D)>) -> Self { if one_of.value.is::() { E4::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E4::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E4::C(*one_of.value.downcast().unwrap()) } else { E4::D(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D> From<&'a OneOf<(A, B, C, D)>> for E4<&'a A, &'a B, &'a C, &'a D> where A: 'static, B: 'static, C: 'static, D: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D)>) -> Self { if one_of.value.is::() { E4::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E4::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E4::C(one_of.value.downcast_ref().unwrap()) } else { E4::D(one_of.value.downcast_ref().unwrap()) } } } impl From> for E5 where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, { fn from(one_of: OneOf<(A, B, C, D, E)>) -> Self { if one_of.value.is::() { E5::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E5::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E5::C(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E5::D(*one_of.value.downcast().unwrap()) } else { E5::E(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D, E> From<&'a OneOf<(A, B, C, D, E)>> for E5<&'a A, &'a B, &'a C, &'a D, &'a E> where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D, E)>) -> Self { if one_of.value.is::() { E5::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E5::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E5::C(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E5::D(one_of.value.downcast_ref().unwrap()) } else { E5::E(one_of.value.downcast_ref().unwrap()) } } } impl From> for E6 where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, { fn from(one_of: OneOf<(A, B, C, D, E, F)>) -> Self { if one_of.value.is::() { E6::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E6::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E6::C(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E6::D(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E6::E(*one_of.value.downcast().unwrap()) } else { E6::F(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D, E, F> From<&'a OneOf<(A, B, C, D, E, F)>> for E6<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F> where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D, E, F)>) -> Self { if one_of.value.is::() { E6::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E6::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E6::C(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E6::D(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E6::E(one_of.value.downcast_ref().unwrap()) } else { E6::F(one_of.value.downcast_ref().unwrap()) } } } impl From> for E7 where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, { fn from(one_of: OneOf<(A, B, C, D, E, F, G)>) -> Self { if one_of.value.is::() { E7::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E7::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E7::C(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E7::D(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E7::E(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E7::F(*one_of.value.downcast().unwrap()) } else { E7::G(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D, E, F, G> From<&'a OneOf<(A, B, C, D, E, F, G)>> for E7<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G> where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D, E, F, G)>) -> Self { if one_of.value.is::() { E7::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E7::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E7::C(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E7::D(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E7::E(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E7::F(one_of.value.downcast_ref().unwrap()) } else { E7::G(one_of.value.downcast_ref().unwrap()) } } } impl From> for E8 where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, H: 'static, { fn from(one_of: OneOf<(A, B, C, D, E, F, G, H)>) -> Self { if one_of.value.is::() { E8::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::C(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::D(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::E(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::F(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E8::G(*one_of.value.downcast().unwrap()) } else { E8::H(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D, E, F, G, H> From<&'a OneOf<(A, B, C, D, E, F, G, H)>> for E8<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H> where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, H: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D, E, F, G, H)>) -> Self { if one_of.value.is::() { E8::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::C(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::D(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::E(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::F(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E8::G(one_of.value.downcast_ref().unwrap()) } else { E8::H(one_of.value.downcast_ref().unwrap()) } } } impl From> for E9 where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, H: 'static, I: 'static, { fn from(one_of: OneOf<(A, B, C, D, E, F, G, H, I)>) -> Self { if one_of.value.is::() { E9::A(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::B(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::C(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::D(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::E(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::F(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::G(*one_of.value.downcast().unwrap()) } else if one_of.value.is::() { E9::H(*one_of.value.downcast().unwrap()) } else { E9::I(*one_of.value.downcast().unwrap()) } } } impl<'a, A, B, C, D, E, F, G, H, I> From<&'a OneOf<(A, B, C, D, E, F, G, H, I)>> for E9<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H, &'a I> where A: 'static, B: 'static, C: 'static, D: 'static, E: 'static, F: 'static, G: 'static, H: 'static, I: 'static, { fn from(one_of: &'a OneOf<(A, B, C, D, E, F, G, H, I)>) -> Self { if one_of.value.is::() { E9::A(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::B(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::C(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::D(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::E(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::F(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::G(one_of.value.downcast_ref().unwrap()) } else if one_of.value.is::() { E9::H(one_of.value.downcast_ref().unwrap()) } else { E9::I(one_of.value.downcast_ref().unwrap()) } } } ================================================ FILE: src/type_set.rs ================================================ //! Type-level set inclusion and difference, inspired by frunk's approach: use core::any::Any; use core::fmt; use std::error::Error; use crate::{Cons, End, Recurse}; /* ------------------------- std::error::Error support ----------------------- */ pub trait ErrorFold { fn source_fold(any: &Box) -> Option<&(dyn Error + 'static)>; #[cfg(feature = "error_provide")] fn provide_fold<'a>(any: &'a Box, request: &mut std::error::Request<'a>); } impl ErrorFold for End { fn source_fold(_: &Box) -> Option<&(dyn Error + 'static)> { unreachable!("source_fold called on End"); } #[cfg(feature = "error_provide")] fn provide_fold<'a>(_: &Box, _: &mut std::error::Request<'a>) { unreachable!("provide_fold called on End"); } } impl Error for Cons where Head: Error, Tail: Error, { } impl ErrorFold for Cons where Cons: Error, Head: 'static + Error, Tail: ErrorFold, { fn source_fold(any: &Box) -> Option<&(dyn Error + 'static)> { if let Some(head_ref) = any.downcast_ref::() { head_ref.source() } else { Tail::source_fold(any) } } #[cfg(feature = "error_provide")] fn provide_fold<'a>(any: &'a Box, request: &mut std::error::Request<'a>) { if let Some(head_ref) = any.downcast_ref::() { head_ref.provide(request) } else { Tail::provide_fold(any, request) } } } /* ------------------------- Display support ----------------------- */ impl fmt::Display for Cons where Head: fmt::Display, Tail: fmt::Display, { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { unreachable!("Display called for Cons which is not constructable") } } impl fmt::Display for End { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { unreachable!("Display::fmt called for an End, which is not constructible.") } } pub trait DisplayFold { fn display_fold(any: &Box, formatter: &mut fmt::Formatter<'_>) -> fmt::Result; } impl DisplayFold for End { fn display_fold(_: &Box, _: &mut fmt::Formatter<'_>) -> fmt::Result { unreachable!("display_fold called on End"); } } impl DisplayFold for Cons where Cons: fmt::Display, Head: 'static + fmt::Display, Tail: DisplayFold, { fn display_fold(any: &Box, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(head_ref) = any.downcast_ref::() { head_ref.fmt(formatter) } else { Tail::display_fold(any, formatter) } } } /* ------------------------- Debug support ----------------------- */ pub trait DebugFold { fn debug_fold(any: &Box, formatter: &mut fmt::Formatter<'_>) -> fmt::Result; } impl DebugFold for End { fn debug_fold(_: &Box, _: &mut fmt::Formatter<'_>) -> fmt::Result { unreachable!("debug_fold called on End"); } } impl DebugFold for Cons where Cons: fmt::Debug, Head: 'static + fmt::Debug, Tail: DebugFold, { fn debug_fold(any: &Box, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(head_ref) = any.downcast_ref::() { head_ref.fmt(formatter) } else { Tail::debug_fold(any, formatter) } } } /* ------------------------- Clone support ----------------------- */ pub trait CloneFold { fn clone_fold(any: &Box) -> Box; } impl Clone for End { fn clone(&self) -> End { unreachable!("clone called for End"); } } impl Clone for Cons where Head: 'static + Clone, Tail: CloneFold, { fn clone(&self) -> Self { unreachable!("clone called for Cons which is not constructable"); } } impl CloneFold for End { fn clone_fold(_: &Box) -> Box { unreachable!("clone_fold called on End"); } } impl CloneFold for Cons where Head: 'static + Clone, Tail: CloneFold, { fn clone_fold(any: &Box) -> Box { if let Some(head_ref) = any.downcast_ref::() { Box::new(head_ref.clone()) } else { Tail::clone_fold(any) } } } fn _clone_test() { fn is_clone() {} type T0 = <(String, u32) as TypeSet>::Variants; is_clone::(); } /* ------------------------- Any::is support ----------------------- */ pub trait IsFold { fn is_fold(any: &Box) -> bool; } impl IsFold for End { fn is_fold(_: &Box) -> bool { false } } impl IsFold for Cons where Head: 'static, Tail: IsFold, { fn is_fold(any: &Box) -> bool { if any.is::() { true } else { Tail::is_fold(any) } } } /* ------------------------- TypeSet implemented for tuples ----------------------- */ pub trait TypeSet { type Variants: TupleForm; type Enum; type EnumRef<'a> where Self: 'a; } impl TypeSet for () { type Variants = End; type Enum = E0; type EnumRef<'a> = E0 where Self: 'a; } impl TypeSet for (A,) { type Variants = Cons; type Enum = E1; type EnumRef<'a> = E1<&'a A> where Self: 'a; } impl TypeSet for (A, B) { type Variants = Cons>; type Enum = E2; type EnumRef<'a> = E2<&'a A, &'a B> where Self: 'a; } impl TypeSet for (A, B, C) { type Variants = Cons>>; type Enum = E3; type EnumRef<'a> = E3<&'a A, &'a B, &'a C> where Self: 'a; } impl TypeSet for (A, B, C, D) { type Variants = Cons>>>; type Enum = E4; type EnumRef<'a> = E4<&'a A, &'a B, &'a C, &'a D> where Self: 'a; } impl TypeSet for (A, B, C, D, E) { type Variants = Cons>>>>; type Enum = E5; type EnumRef<'a> = E5<&'a A, &'a B, &'a C, &'a D, &'a E> where Self: 'a; } impl TypeSet for (A, B, C, D, E, F) { type Variants = Cons>>>>>; type Enum = E6; type EnumRef<'a> = E6<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F> where Self: 'a; } impl TypeSet for (A, B, C, D, E, F, G) { type Variants = Cons>>>>>>; type Enum = E7; type EnumRef<'a> = E7<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G> where Self: 'a; } impl TypeSet for (A, B, C, D, E, F, G, H) { type Variants = Cons>>>>>>>; type Enum = E8; type EnumRef<'a> = E8<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H> where Self: 'a; } impl TypeSet for (A, B, C, D, E, F, G, H, I) { type Variants = Cons>>>>>>>>; type Enum = E9; type EnumRef<'a> = E9<&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H, &'a I> where Self: 'a; } /* ------------------------- TupleForm implemented for TypeSet ----------------------- */ pub trait TupleForm { type Tuple: TypeSet; } impl TupleForm for End { type Tuple = (); } impl TupleForm for Cons { type Tuple = (A,); } impl TupleForm for Cons> { type Tuple = (A, B); } impl TupleForm for Cons>> { type Tuple = (A, B, C); } impl TupleForm for Cons>>> { type Tuple = (A, B, C, D); } impl TupleForm for Cons>>>> { type Tuple = (A, B, C, D, E); } impl TupleForm for Cons>>>>> { type Tuple = (A, B, C, D, E, F); } impl TupleForm for Cons>>>>>> { type Tuple = (A, B, C, D, E, F, G); } impl TupleForm for Cons>>>>>>> { type Tuple = (A, B, C, D, E, F, G, H); } impl TupleForm for Cons>>>>>>>> { type Tuple = (A, B, C, D, E, F, G, H, I); } /* ------------------------- Lifted ----------------------- */ pub enum E0 {} pub enum E1 { A(A), } impl From for E1 { fn from(a: A) -> E1 { E1::A(a) } } pub enum E2 { A(A), B(B), } pub enum E3 { A(A), B(B), C(C), } pub enum E4 { A(A), B(B), C(C), D(D), } pub enum E5 { A(A), B(B), C(C), D(D), E(E), } pub enum E6 { A(A), B(B), C(C), D(D), E(E), F(F), } pub enum E7 { A(A), B(B), C(C), D(D), E(E), F(F), G(G), } pub enum E8 { A(A), B(B), C(C), D(D), E(E), F(F), G(G), H(H), } pub enum E9 { A(A), B(B), C(C), D(D), E(E), F(F), G(G), H(H), I(I), } /* ------------------------- Contains ----------------------- */ /// A trait that assists with compile-time type set inclusion testing. /// The `Index` parameter is either `End` or `Cons<...>` depending on /// whether the trait implementation is a base case or the recursive /// case. pub trait Contains {} /// Base case implementation for when the Cons Head is T. impl Contains for Cons {} /// Recursive case for when the Cons Tail contains T. impl Contains> for Cons where Tail: Contains { } /* ------------------------- Narrow ----------------------- */ /// A trait for pulling a specific type out of a Variants at compile-time /// and having access to the other types as the Remainder. pub trait Narrow: TupleForm { type Remainder: TupleForm; } /// Base case where the search Target is in the Head of the Variants. impl Narrow for Cons where Tail: TupleForm, Cons: TupleForm, { type Remainder = Tail; } /// Recursive case where the search Target is in the Tail of the Variants. impl Narrow> for Cons where Tail: Narrow, Tail: TupleForm, Cons: TupleForm, Cons>::Remainder>: TupleForm, { type Remainder = Cons>::Remainder>; } fn _narrow_test() { fn can_narrow() where Types: Narrow, { } type T0 = <(u32, String) as TypeSet>::Variants; can_narrow::(); can_narrow::, _>(); } /* ------------------------- SupersetOf ----------------------- */ /// When all types in a Variants are present in a second Variants pub trait SupersetOf { type Remainder: TupleForm; } /// Base case impl SupersetOf for T { type Remainder = T; } /// Recursive case - more complex because we have to reason about the Index itself as a /// heterogenous list. impl SupersetOf, Cons> for Cons where Cons: Narrow, as Narrow>::Remainder: SupersetOf, { type Remainder = < as Narrow>::Remainder as SupersetOf< SubTail, TailIndex, >>::Remainder; } fn _superset_test() { fn is_superset() where S1: SupersetOf, { } type T0 = <(u32,) as TypeSet>::Variants; type T1A = <(u32, String) as TypeSet>::Variants; type T1B = <(String, u32) as TypeSet>::Variants; type T2 = <(String, i32, u32) as TypeSet>::Variants; type T3 = <(Vec, Vec, u32, f32, String, f64, i32) as TypeSet>::Variants; is_superset::(); is_superset::(); is_superset::(); is_superset::(); is_superset::(); is_superset::(); is_superset::(); is_superset::::Variants, _>(); is_superset::::Variants, _>(); is_superset::::Variants, _>(); is_superset::, Vec, f32, f64, i32) as TypeSet>::Variants, _>(); is_superset::(); is_superset::(); is_superset::(); type T5sup = <(u8, u16, u32, u64, u128) as TypeSet>::Variants; type T5sub = <(u8, u128) as TypeSet>::Variants; type T5rem = <(u16, u32, u64) as TypeSet>::Variants; is_superset::(); } ================================================ FILE: tests/usability.rs ================================================ use terrors::OneOf; #[derive(Debug)] struct NotEnoughMemory; #[derive(Debug)] struct Timeout; #[derive(Debug)] struct RetriesExhausted; #[test] fn retry() { fn inner() -> Result<(), OneOf<(NotEnoughMemory, RetriesExhausted)>> { for _ in 0..3 { let Err(err) = does_stuff() else { return Ok(()); }; match err.narrow::() { Ok(_timeout) => continue, Err(allocation_oneof) => { println!("didn't get Timeout, now trying to get NotEnoughMemory"); let allocation_oneof: OneOf<(NotEnoughMemory,)> = allocation_oneof; let allocation = allocation_oneof.narrow::().unwrap(); return Err(OneOf::new(allocation)); } } } Err(OneOf::new(RetriesExhausted)) } let _ = dbg!(inner()); } fn does_stuff() -> Result<(), OneOf<(NotEnoughMemory, Timeout)>> { // TODO Try impl after superset type work let _allocation = match allocates() { Ok(a) => a, Err(e) => return Err(e.broaden()), }; // TODO Try impl after superset type work let _chat = match chats() { Ok(c) => c, Err(e) => return Err(OneOf::new(e)), }; Ok(()) } fn allocates() -> Result<(), OneOf<(NotEnoughMemory,)>> { let result: Result<(), NotEnoughMemory> = Err(NotEnoughMemory); result?; Ok(()) } fn chats() -> Result<(), Timeout> { Err(Timeout) } #[test] fn smoke() { let o_1: OneOf<(u32, String)> = OneOf::new(5_u32); let _narrowed_1: u32 = o_1.narrow::().unwrap(); let o_2: OneOf<(String, u32)> = OneOf::new(5_u32); let _narrowed_2: u32 = o_2.narrow::().unwrap(); let o_3: OneOf<(String, u32)> = OneOf::new("5".to_string()); let _narrowed_3: OneOf<(String,)> = o_3.narrow::().unwrap_err(); let o_4: OneOf<(String, u32)> = OneOf::new("5".to_string()); let _: String = o_4.narrow().unwrap(); let o_5: OneOf<(String, u32)> = OneOf::new("5".to_string()); o_5.narrow::().unwrap(); let o_6: OneOf<(String, u32)> = OneOf::new("5".to_string()); let o_7: OneOf<(u32, String)> = o_6.broaden(); let o_8: OneOf<(String, u32)> = o_7.subset().unwrap(); let _: OneOf<(u32, String)> = o_8.subset().unwrap(); let o_9: OneOf<(u8, u16, u32)> = OneOf::new(3_u32); let _: Result, OneOf<(u8, u32)>> = o_9.subset(); let o_10: OneOf<(u8, u16, u32)> = OneOf::new(3_u32); let _: Result> = o_10.narrow(); } #[test] fn debug() { use std::error::Error; use std::io; let o_1: OneOf<(u32, String)> = OneOf::new(5_u32); // Debug is implemented if all types in the type set implement Debug dbg!(&o_1); // Display is implemented if all types in the type set implement Display println!("{}", o_1); type E = io::Error; let e = io::Error::new(io::ErrorKind::Other, "wuaaaaahhhzzaaaaaaaa"); let o_2: OneOf<(E,)> = OneOf::new(e); // std::error::Error is implemented if all types in the type set implement it dbg!(o_2.source()); let o_3: OneOf<(u32, String)> = OneOf::new("hey".to_string()); dbg!(o_3); } #[test] fn multi_match() { use terrors::E2; let o_1: OneOf<(u32, String)> = OneOf::new(5_u32); match o_1.as_enum() { E2::A(u) => { println!("handling {u}: u32") } E2::B(s) => { println!("handling {s}: String") } } match o_1.to_enum() { E2::A(u) => { println!("handling {u}: u32") } E2::B(s) => { println!("handling {s}: String") } } } #[test] fn multi_narrow() { use terrors::E2; struct Timeout {} struct Backoff {} let o_1: OneOf<(u8, u16, u32, u64, u128)> = OneOf::new(5_u32); let _narrow_res: Result, OneOf<(u16, u32, u64)>> = o_1.subset(); let o_2: OneOf<(u8, u16, Backoff, Timeout, u32, u64, u128)> = OneOf::new(Timeout {}); match o_2.subset::<(Timeout, Backoff), _>().unwrap().to_enum() { E2::A(Timeout {}) => { println!(":)"); } E2::B(Backoff {}) => { unreachable!() } } }