├── .cargo └── config.toml ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src ├── boxed_error.rs ├── lib.rs └── thin_box.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustdocflags = ["--cfg", "docsrs"] 3 | 4 | [env] 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rkyv 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: "0 10 * * *" 9 | 10 | permissions: 11 | contents: read 12 | 13 | env: 14 | RUSTFLAGS: -Dwarnings 15 | 16 | jobs: 17 | features: 18 | name: Features / ${{ matrix.alloc }} ${{ matrix.derive }} 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | alloc: 24 | - '' 25 | - alloc 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: dtolnay/rust-toolchain@stable 30 | - run: cargo test --verbose --tests --no-default-features --features "${{ matrix.std }}" 31 | 32 | toolchain: 33 | name: Toolchain / ${{ matrix.toolchain }} ${{ matrix.opt }} 34 | runs-on: ubuntu-latest 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | toolchain: 39 | - stable 40 | - beta 41 | - nightly 42 | opt: 43 | - '' 44 | - --release 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: dtolnay/rust-toolchain@master 49 | with: 50 | toolchain: ${{ matrix.toolchain }} 51 | - run: cargo test --verbose ${{ matrix.opt }} 52 | 53 | miri: 54 | name: Miri / ${{ matrix.opt }} 55 | runs-on: ubuntu-latest 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | opt: 60 | - '' 61 | - --release 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | - uses: dtolnay/rust-toolchain@miri 66 | - run: cargo miri setup 67 | - run: cargo miri test ${{ matrix.opt }} --verbose 68 | env: 69 | MIRIFLAGS: -Zmiri-disable-stacked-borrows -Zmiri-tree-borrows 70 | 71 | test: 72 | name: Test / ${{ matrix.target }} ${{ matrix.opt }} 73 | runs-on: ${{ matrix.os }} 74 | 75 | strategy: 76 | fail-fast: false 77 | matrix: 78 | opt: 79 | - '' 80 | - --release 81 | include: 82 | - os: ubuntu-latest 83 | target: x86_64-unknown-linux-gnu 84 | - os: macos-latest 85 | target: aarch64-apple-darwin 86 | - os: windows-latest 87 | target: x86_64-pc-windows-msvc 88 | 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: dtolnay/rust-toolchain@stable 92 | - run: cargo test ${{ matrix.opt }} 93 | 94 | cross: 95 | name: Cross / ${{ matrix.target }} 96 | runs-on: ubuntu-latest 97 | 98 | strategy: 99 | fail-fast: false 100 | matrix: 101 | target: 102 | - i686-unknown-linux-gnu 103 | - i586-unknown-linux-gnu 104 | - armv7-unknown-linux-gnueabihf 105 | - aarch64-unknown-linux-gnu 106 | - thumbv6m-none-eabi 107 | 108 | steps: 109 | - uses: actions/checkout@v4 110 | - uses: dtolnay/rust-toolchain@stable 111 | - run: cargo install cross 112 | - run: cross build --no-default-features --features "alloc" --target ${{ matrix.target }} --verbose 113 | 114 | format: 115 | name: Format 116 | runs-on: ubuntu-latest 117 | 118 | steps: 119 | - uses: actions/checkout@v4 120 | - uses: dtolnay/rust-toolchain@nightly 121 | with: 122 | components: rustfmt 123 | - run: cargo fmt --check 124 | 125 | clippy: 126 | name: Clippy 127 | runs-on: ubuntu-latest 128 | 129 | steps: 130 | - uses: actions/checkout@v4 131 | - uses: dtolnay/rust-toolchain@nightly 132 | with: 133 | components: clippy 134 | - run: cargo clippy 135 | 136 | doc: 137 | name: Doc 138 | runs-on: ubuntu-latest 139 | 140 | steps: 141 | - uses: actions/checkout@v4 142 | - uses: dtolnay/rust-toolchain@nightly 143 | - run: cargo doc 144 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rancor" 3 | description = "Scalable and efficient error handling without type composition" 4 | version = "0.1.1" 5 | authors = ["David Koloski "] 6 | edition = "2021" 7 | rust-version = "1.81" 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/rkyv/rancor" 11 | keywords = ["error", "error-handling", "no_std"] 12 | categories = ["rust-patterns", "no-std"] 13 | documentation = "https://docs.rs/rancor" 14 | 15 | [dependencies] 16 | ptr_meta = { version = "0.3", default-features = false, features = ["derive"] } 17 | 18 | [features] 19 | default = ["alloc"] 20 | alloc = [] 21 | 22 | [patch.crates-io] 23 | ptr_meta = { git = "https://github.com/rkyv/ptr_meta" } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 David Koloski 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `rancor` 2 | 3 | [![crates.io badge]][crates.io] [![docs badge]][docs] [![license badge]][license] 4 | 5 | [crates.io badge]: https://img.shields.io/crates/v/rancor.svg 6 | [crates.io]: https://crates.io/crates/rancor 7 | [docs badge]: https://img.shields.io/docsrs/rancor 8 | [docs]: https://docs.rs/rancor 9 | [license badge]: https://img.shields.io/badge/license-MIT-blue.svg 10 | [license]: https://github.com/rkyv/rancor/blob/master/LICENSE 11 | 12 | Rancor provides scalable and efficient error handling without using type 13 | composition. 14 | 15 | ## Documentation 16 | 17 | - [rancor](https://docs.rs/rancor), a scalable and efficient error handling 18 | library 19 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | comment_width = 80 3 | wrap_comments = true 4 | group_imports = "StdExternalCrate" 5 | imports_granularity = "Crate" 6 | condense_wildcard_suffixes = true 7 | error_on_line_overflow = true 8 | error_on_unformatted = true 9 | format_code_in_doc_comments = true 10 | format_macro_matchers = true 11 | format_macro_bodies = true 12 | format_strings = true 13 | hex_literal_case = "Lower" 14 | normalize_comments = true 15 | use_field_init_shorthand = true 16 | -------------------------------------------------------------------------------- /src/boxed_error.rs: -------------------------------------------------------------------------------- 1 | use core::{error, fmt}; 2 | 3 | use crate::{thin_box::ThinBox, Source, Trace}; 4 | 5 | #[ptr_meta::pointee] 6 | trait ErrorTrace: fmt::Debug + fmt::Display + Send + Sync + 'static {} 7 | 8 | impl ErrorTrace for T where 9 | T: fmt::Debug + fmt::Display + Send + Sync + 'static + ?Sized 10 | { 11 | } 12 | 13 | #[derive(Debug)] 14 | struct ErrorWithTrace { 15 | error: BoxedError, 16 | trace: ThinBox, 17 | } 18 | 19 | impl fmt::Display for ErrorWithTrace { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "{}", self.error)?; 22 | write!(f, "trace: {}", self.trace)?; 23 | 24 | Ok(()) 25 | } 26 | } 27 | 28 | impl error::Error for ErrorWithTrace { 29 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 30 | self.error.inner.source() 31 | } 32 | } 33 | 34 | /// An error type that preserves all detailed error messages. It is optimized 35 | /// to fit in a single pointer. 36 | #[derive(Debug)] 37 | pub struct BoxedError { 38 | inner: ThinBox, 39 | } 40 | 41 | impl fmt::Display for BoxedError { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | write!(f, "{}", self.inner) 44 | } 45 | } 46 | 47 | impl error::Error for BoxedError { 48 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 49 | self.inner.source() 50 | } 51 | } 52 | 53 | impl Trace for BoxedError { 54 | fn trace(self, trace: R) -> Self 55 | where 56 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 57 | { 58 | Self::new(ErrorWithTrace { 59 | error: self, 60 | // SAFETY: The provided closure returns the same pointer unsized to 61 | // a `dyn ErrorTrace`. 62 | trace: unsafe { 63 | ThinBox::new_unchecked(trace, |ptr| ptr as *mut _) 64 | }, 65 | }) 66 | } 67 | } 68 | 69 | impl Source for BoxedError { 70 | fn new(source: T) -> Self { 71 | Self { 72 | // SAFETY: The provided closure returns the same pointer unsized to 73 | // a `dyn Error`. 74 | inner: unsafe { 75 | ThinBox::new_unchecked(source, |ptr| ptr as *mut _) 76 | }, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # rancor 2 | //! 3 | //! rancor provides scalable and efficient error handling without using type 4 | //! composition. This makes it best-suited for situations where: 5 | //! 6 | //! - Programmatic error introspection is not useful 7 | //! - Functions may error, but succeed most of the time 8 | //! - Errors should provide as much useful detail as possible when emitted 9 | //! - Use cases include both `no_std` and targets with support for `std` 10 | //! 11 | //! ## Features 12 | //! 13 | //! - `alloc`: Provides the [`BoxedError`] type. Enabled by default. 14 | 15 | #![deny( 16 | future_incompatible, 17 | missing_docs, 18 | nonstandard_style, 19 | unsafe_op_in_unsafe_fn, 20 | unused, 21 | warnings, 22 | clippy::all, 23 | clippy::missing_safety_doc, 24 | clippy::undocumented_unsafe_blocks, 25 | rustdoc::broken_intra_doc_links, 26 | rustdoc::missing_crate_level_docs 27 | )] 28 | #![no_std] 29 | #![cfg_attr(all(docsrs, not(doctest)), feature(doc_cfg))] 30 | 31 | #[cfg(feature = "alloc")] 32 | extern crate alloc; 33 | 34 | #[cfg(feature = "alloc")] 35 | mod boxed_error; 36 | #[cfg(feature = "alloc")] 37 | mod thin_box; 38 | 39 | use core::{ 40 | error, fmt, 41 | hint::unreachable_unchecked, 42 | marker::PhantomData, 43 | ops::{Deref, DerefMut}, 44 | }; 45 | 46 | /// An error type which can add additional "trace" information to itself. 47 | /// 48 | /// Some functions only add additional context to errors created by other 49 | /// functions, rather than creating errors themselves. With generics, it's 50 | /// therefore possible to have a generic function which can produce errors with 51 | /// some type arguments but not with others. In these cases, `Trace` allows 52 | /// those functions to add context if an error can occur, and compile out the 53 | /// context if the error type is [`Infallible`] or [`Panic`]. 54 | /// 55 | /// # Example 56 | /// 57 | /// ``` 58 | /// use rancor::{ResultExt, Trace}; 59 | /// 60 | /// trait Print { 61 | /// fn print(&self, message: &str) -> Result<(), E>; 62 | /// } 63 | /// 64 | /// fn print_hello_world, E: Trace>(printer: &T) -> Result<(), E> { 65 | /// printer.print("hello").trace("failed to print hello")?; 66 | /// printer.print("world").trace("failed to print world")?; 67 | /// Ok(()) 68 | /// } 69 | /// ``` 70 | pub trait Trace: Sized + Send + Sync + 'static { 71 | /// Adds an additional trace to this error, returning a new error. 72 | fn trace(self, trace: R) -> Self 73 | where 74 | R: fmt::Debug + fmt::Display + Send + Sync + 'static; 75 | } 76 | 77 | /// An error type which can be uniformly constructed from an [`Error`] and 78 | /// additional trace information. 79 | /// 80 | /// # Example 81 | /// 82 | /// ``` 83 | /// use core::{error::Error, fmt}; 84 | /// 85 | /// use rancor::{fail, Source}; 86 | /// 87 | /// #[derive(Debug)] 88 | /// struct DivideByZeroError; 89 | /// 90 | /// impl fmt::Display for DivideByZeroError { 91 | /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | /// write!(f, "attempted to divide by zero") 93 | /// } 94 | /// } 95 | /// 96 | /// impl Error for DivideByZeroError {} 97 | /// 98 | /// fn try_divide(a: i32, b: i32) -> Result { 99 | /// if b == 0 { 100 | /// fail!(DivideByZeroError); 101 | /// } 102 | /// Ok(a / b) 103 | /// } 104 | /// ``` 105 | pub trait Source: Trace + error::Error { 106 | /// Returns a new `Self` using the given [`Error`]. 107 | /// 108 | /// Depending on the specific implementation, this may box the error, 109 | /// immediately emit a diagnostic, or discard it and only remember that some 110 | /// error occurred. 111 | fn new(source: T) -> Self; 112 | } 113 | 114 | /// A type with fallible operations that return its associated error type. 115 | /// 116 | /// `Fallible` turns an error type parameter into an associated type of another 117 | /// parameter. You can equip an existing type with a `Fallible` implementation 118 | /// by wrapping it in a [`Strategy`]. 119 | /// 120 | /// # Example 121 | /// 122 | /// ``` 123 | /// use rancor::{Failure, Fallible, Strategy}; 124 | /// 125 | /// trait Operator::Error> { 126 | /// fn operate(&self, lhs: i32, rhs: i32) -> Result; 127 | /// } 128 | /// 129 | /// impl + ?Sized, E> Operator for Strategy { 130 | /// fn operate(&self, lhs: i32, rhs: i32) -> Result { 131 | /// T::operate(self, lhs, rhs) 132 | /// } 133 | /// } 134 | /// 135 | /// struct Add; 136 | /// 137 | /// impl Operator for Add { 138 | /// fn operate(&self, lhs: i32, rhs: i32) -> Result { 139 | /// Ok(lhs + rhs) 140 | /// } 141 | /// } 142 | /// 143 | /// fn operate_one_one( 144 | /// operator: &T, 145 | /// ) -> Result { 146 | /// operator.operate(1, 1) 147 | /// } 148 | /// 149 | /// assert_eq!( 150 | /// operate_one_one(Strategy::<_, Failure>::wrap(&mut Add)), 151 | /// Ok(2) 152 | /// ); 153 | /// ``` 154 | pub trait Fallible { 155 | /// The error type associated with this type's operations. 156 | type Error; 157 | } 158 | 159 | /// Equips a type with a `Fallible` implementation that chooses `E` as its error 160 | /// type. 161 | /// 162 | /// # Example 163 | /// 164 | /// ``` 165 | /// use rancor::{Failure, Fallible, Strategy}; 166 | /// 167 | /// trait Print::Error> { 168 | /// fn print(&self, message: &str) -> Result<(), E>; 169 | /// } 170 | /// 171 | /// impl + ?Sized, E> Print for Strategy { 172 | /// fn print(&self, message: &str) -> Result<(), E> { 173 | /// T::print(self, message) 174 | /// } 175 | /// } 176 | /// 177 | /// struct StdOut; 178 | /// 179 | /// impl Print for StdOut { 180 | /// fn print(&self, message: &str) -> Result<(), E> { 181 | /// println!("{message}"); 182 | /// Ok(()) 183 | /// } 184 | /// } 185 | /// 186 | /// Strategy::<_, Failure>::wrap(&mut StdOut) 187 | /// .print("hello world") 188 | /// .unwrap(); 189 | /// ``` 190 | #[repr(transparent)] 191 | pub struct Strategy { 192 | _error: PhantomData, 193 | inner: T, 194 | } 195 | 196 | impl Fallible for Strategy { 197 | type Error = E; 198 | } 199 | 200 | impl Strategy { 201 | /// Wraps the given mutable reference, returning a mutable reference to a 202 | /// `Strategy`. 203 | /// 204 | /// ## Example 205 | /// ``` 206 | /// use core::ops::Deref; 207 | /// 208 | /// use rancor::{Failure, Strategy}; 209 | /// fn test() { 210 | /// struct Inner { 211 | /// value: u64, 212 | /// } 213 | /// 214 | /// let mut inner = Inner { value: 10 }; 215 | /// 216 | /// let inner_value_ptr = &inner.value as *const u64; 217 | /// let strategy: &mut Strategy = 218 | /// Strategy::wrap(&mut inner); 219 | /// let strategy_value_ptr = (&strategy.deref().value) as *const u64; 220 | /// assert_eq!(inner_value_ptr, strategy_value_ptr); 221 | /// // Strategy wraps a type but does not change its memory layout. 222 | /// } 223 | /// 224 | /// test(); 225 | /// ``` 226 | pub fn wrap(inner: &mut T) -> &mut Self { 227 | // SAFETY: `Strategy` is `repr(transparent)` and so has the same layout 228 | // as `T`. The input and output lifetimes are the same, so mutable 229 | // aliasing rules will be upheld. Finally, because the inner `T` is the 230 | // final element of `Strategy`, the pointer metadata of the two pointers 231 | // will be the same. 232 | unsafe { core::mem::transmute::<&mut T, &mut Self>(inner) } 233 | } 234 | } 235 | 236 | impl Deref for Strategy { 237 | type Target = T; 238 | 239 | fn deref(&self) -> &Self::Target { 240 | &self.inner 241 | } 242 | } 243 | 244 | impl DerefMut for Strategy { 245 | fn deref_mut(&mut self) -> &mut Self::Target { 246 | &mut self.inner 247 | } 248 | } 249 | 250 | /// Returns the given error from this function. 251 | /// 252 | /// The current function must return a `Result<_, E>` where `E` implements 253 | /// [`Source`]. 254 | /// 255 | /// # Example 256 | /// 257 | /// ``` 258 | /// use core::{error::Error, fmt}; 259 | /// 260 | /// use rancor::{fail, Source}; 261 | /// 262 | /// #[derive(Debug)] 263 | /// struct DivideByZeroError; 264 | /// 265 | /// impl fmt::Display for DivideByZeroError { 266 | /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 267 | /// write!(f, "attempted to divide by zero") 268 | /// } 269 | /// } 270 | /// 271 | /// impl Error for DivideByZeroError {} 272 | /// 273 | /// fn divide(a: i32, b: i32) -> Result { 274 | /// if b == 0 { 275 | /// fail!(DivideByZeroError); 276 | /// } 277 | /// Ok(a / b) 278 | /// } 279 | /// ``` 280 | #[macro_export] 281 | macro_rules! fail { 282 | ($($x:tt)*) => { 283 | return ::core::result::Result::Err($crate::Source::new($($x)*)); 284 | }; 285 | } 286 | 287 | /// Helper methods for `Result`s. 288 | pub trait ResultExt { 289 | /// Returns a `Result` with this error type converted to `U`. 290 | /// 291 | /// # Example 292 | /// 293 | /// ``` 294 | /// use rancor::{Failure, ResultExt as _}; 295 | /// 296 | /// let result = "1_000".parse::().into_error::(); 297 | /// ``` 298 | fn into_error(self) -> Result 299 | where 300 | U: Source, 301 | E: error::Error + Send + Sync + 'static; 302 | 303 | /// Returns a `Result` with this error type converted to `U` and with an 304 | /// additional `trace` message added. 305 | /// 306 | /// # Example 307 | /// 308 | /// ``` 309 | /// use rancor::{BoxedError, ResultExt as _}; 310 | /// 311 | /// let result = "1_000" 312 | /// .parse::() 313 | /// .into_trace::("while parsing 1_000"); 314 | /// ``` 315 | fn into_trace(self, trace: R) -> Result 316 | where 317 | U: Source, 318 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 319 | E: error::Error + Send + Sync + 'static; 320 | 321 | /// Returns a `Result` with this error type converted to `U` and with an 322 | /// additional trace message added by evaluating the given function `f`. The 323 | /// function is evaluated only if an error occurred. 324 | /// 325 | /// # Example 326 | /// 327 | /// ``` 328 | /// use rancor::{BoxedError, ResultExt as _}; 329 | /// 330 | /// let input = "1_000"; 331 | /// let result = 332 | /// input 333 | /// .parse::() 334 | /// .into_with_trace::(|| { 335 | /// format!("while parsing {input}") 336 | /// }); 337 | /// ``` 338 | fn into_with_trace(self, f: F) -> Result 339 | where 340 | U: Source, 341 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 342 | F: FnOnce() -> R, 343 | E: error::Error + Send + Sync + 'static; 344 | 345 | /// Adds an additional `trace` message to the error value of this type. 346 | /// 347 | /// # Example 348 | /// 349 | /// ``` 350 | /// use rancor::{BoxedError, ResultExt as _}; 351 | /// 352 | /// let result = "1_000" 353 | /// .parse::() 354 | /// .into_error::() 355 | /// .trace("while parsing 1_000"); 356 | /// ``` 357 | fn trace(self, trace: R) -> Result 358 | where 359 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 360 | E: Trace; 361 | 362 | /// Adds an additional trace message to the error value of this type by 363 | /// evaluating the given function `f`. The function is evaluated only if an 364 | /// error occurred. 365 | /// 366 | /// # Example 367 | /// 368 | /// ``` 369 | /// use rancor::{BoxedError, ResultExt as _}; 370 | /// 371 | /// let input = "1_000"; 372 | /// let result = input 373 | /// .parse::() 374 | /// .into_error::() 375 | /// .with_trace(|| format!("while parsing {input}")); 376 | /// ``` 377 | fn with_trace(self, f: F) -> Result 378 | where 379 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 380 | F: FnOnce() -> R, 381 | E: Trace; 382 | 383 | /// Safely unwraps a result that is always `Ok`. 384 | /// 385 | /// In order to call this method, the error type of this `Result` must be a 386 | /// [`Never`] type. 387 | /// 388 | /// # Example 389 | /// 390 | /// ``` 391 | /// use rancor::{Infallible, ResultExt}; 392 | /// 393 | /// let inner = Ok::(10).always_ok(); 394 | /// ``` 395 | fn always_ok(self) -> T 396 | where 397 | E: Never; 398 | } 399 | 400 | impl ResultExt for Result { 401 | fn into_error(self) -> Result 402 | where 403 | U: Source, 404 | E: error::Error + Send + Sync + 'static, 405 | { 406 | match self { 407 | Ok(x) => Ok(x), 408 | Err(e) => Err(U::new(e)), 409 | } 410 | } 411 | 412 | fn into_trace(self, trace: R) -> Result 413 | where 414 | U: Source, 415 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 416 | E: error::Error + Send + Sync + 'static, 417 | { 418 | match self { 419 | Ok(x) => Ok(x), 420 | Err(e) => Err(U::new(e).trace(trace)), 421 | } 422 | } 423 | 424 | fn into_with_trace(self, f: F) -> Result 425 | where 426 | U: Source, 427 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 428 | F: FnOnce() -> R, 429 | E: error::Error + Send + Sync + 'static, 430 | { 431 | match self { 432 | Ok(x) => Ok(x), 433 | Err(e) => Err(U::new(e).trace(f())), 434 | } 435 | } 436 | 437 | fn trace(self, trace: R) -> Result 438 | where 439 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 440 | E: Trace, 441 | { 442 | match self { 443 | Ok(x) => Ok(x), 444 | Err(e) => Err(e.trace(trace)), 445 | } 446 | } 447 | 448 | fn with_trace(self, f: F) -> Result 449 | where 450 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 451 | F: FnOnce() -> R, 452 | E: Trace, 453 | { 454 | match self { 455 | Ok(x) => Ok(x), 456 | Err(e) => Err(e.trace(f())), 457 | } 458 | } 459 | 460 | fn always_ok(self) -> T 461 | where 462 | E: Never, 463 | { 464 | match self { 465 | Ok(x) => x, 466 | Err(e) => unreachable_checked(e), 467 | } 468 | } 469 | } 470 | 471 | /// Helper methods for `Option`s. 472 | pub trait OptionExt { 473 | /// Returns a `Result` with an error indicating that `Some` was expected but 474 | /// `None` was found. 475 | /// 476 | /// # Example 477 | /// 478 | /// ``` 479 | /// use rancor::{Failure, OptionExt}; 480 | /// 481 | /// let result = Some(10).into_error::(); 482 | /// ``` 483 | fn into_error(self) -> Result 484 | where 485 | E: Source; 486 | 487 | /// Returns a `Result` with an error indicating that `Some` was expected but 488 | /// `None` was found, and with an additional `trace` message added. 489 | /// 490 | /// # Example 491 | /// 492 | /// ``` 493 | /// use rancor::{Failure, OptionExt}; 494 | /// 495 | /// ##[rustfmt::skip] 496 | /// let result = Some(10). 497 | /// into_trace::("while converting Some(10)"); 498 | /// ``` 499 | fn into_trace(self, trace: R) -> Result 500 | where 501 | E: Source, 502 | R: fmt::Debug + fmt::Display + Send + Sync + 'static; 503 | 504 | /// Returns a `Result` with an error indicating that `Some` was expected but 505 | /// `None` was found, and with an additional trace message added by 506 | /// evaluating the given function `f`. The function is evaluated only if an 507 | /// error occurred. 508 | /// 509 | /// # Example 510 | /// 511 | /// ``` 512 | /// use rancor::{Failure, OptionExt}; 513 | /// 514 | /// let input = Some(10); 515 | /// let result = input.into_with_trace::(|| { 516 | /// format!("while converting {input:?}") 517 | /// }); 518 | /// ``` 519 | fn into_with_trace(self, f: F) -> Result 520 | where 521 | E: Source, 522 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 523 | F: FnOnce() -> R; 524 | } 525 | 526 | /// A type that can never be produced. 527 | /// 528 | /// Never types include the unstable `!` type, enums with no variants, or any 529 | /// type that always contains a never type (e.g. a struct with a `Never` field). 530 | /// 531 | /// # Safety 532 | /// 533 | /// It must be impossible to produce a value of this type. 534 | pub unsafe trait Never {} 535 | 536 | /// Consumes a `Never` type, returning a primitive `!`. 537 | /// 538 | /// This is a safe version of [`unreachable_unchecked`] for `Never` types. 539 | /// 540 | /// # Example 541 | /// 542 | /// ``` 543 | /// use rancor::{unreachable_checked, Infallible}; 544 | /// 545 | /// let result = Ok::(10); 546 | /// match result { 547 | /// Ok(i) => println!("i"), 548 | /// Err(e) => unreachable_checked(e), 549 | /// } 550 | /// ``` 551 | #[inline(always)] 552 | pub const fn unreachable_checked(_: T) -> ! { 553 | // SAFETY: Types that implement `Never` cannot be constructed, 554 | // so this is unreachable. 555 | unsafe { unreachable_unchecked() } 556 | } 557 | 558 | #[derive(Debug)] 559 | struct NoneError; 560 | 561 | impl fmt::Display for NoneError { 562 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 563 | write!(f, "`Option` is `None`, expected `Some`") 564 | } 565 | } 566 | 567 | impl error::Error for NoneError {} 568 | 569 | impl OptionExt for Option { 570 | fn into_error(self) -> Result 571 | where 572 | E: Source, 573 | { 574 | match self { 575 | Some(x) => Ok(x), 576 | None => Err(E::new(NoneError)), 577 | } 578 | } 579 | 580 | fn into_trace(self, trace: R) -> Result 581 | where 582 | E: Source, 583 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 584 | { 585 | match self { 586 | Some(x) => Ok(x), 587 | None => Err(E::new(NoneError).trace(trace)), 588 | } 589 | } 590 | 591 | fn into_with_trace(self, f: F) -> Result 592 | where 593 | E: Source, 594 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 595 | F: FnOnce() -> R, 596 | { 597 | match self { 598 | Some(x) => Ok(x), 599 | None => Err(E::new(NoneError).trace(f())), 600 | } 601 | } 602 | } 603 | 604 | /// A re-export of `core::convert::Infallible`. 605 | pub use core::convert::Infallible; 606 | 607 | // SAFETY: `Infallible` is an enum with no variants, and so cannot be produced. 608 | unsafe impl Never for Infallible {} 609 | 610 | impl Trace for Infallible { 611 | fn trace(self, _: R) -> Self 612 | where 613 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 614 | { 615 | match self {} 616 | } 617 | } 618 | 619 | /// An error type that does not occupy any space, panicking on creation instead. 620 | /// 621 | /// Because panicking occurs immediately upon creation, this error type will not 622 | /// print any additional trace information. 623 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 624 | pub enum Panic {} 625 | 626 | // SAFETY: `Panic` is an enum with no variants, and so cannot be produced. 627 | unsafe impl Never for Panic {} 628 | 629 | impl fmt::Display for Panic { 630 | fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { 631 | match *self {} 632 | } 633 | } 634 | 635 | impl error::Error for Panic {} 636 | 637 | impl Trace for Panic { 638 | fn trace(self, _: R) -> Self 639 | where 640 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 641 | { 642 | match self {} 643 | } 644 | } 645 | 646 | impl Source for Panic { 647 | fn new(error: T) -> Self { 648 | panic!("created a new `Panic` from: {error}"); 649 | } 650 | } 651 | 652 | /// An error type that only preserves success or failure, throwing away any more 653 | /// detailed error messages. 654 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 655 | pub struct Failure; 656 | 657 | impl fmt::Display for Failure { 658 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 659 | write!(f, "failed without error information") 660 | } 661 | } 662 | 663 | impl error::Error for Failure {} 664 | 665 | impl Trace for Failure { 666 | fn trace(self, _: R) -> Self 667 | where 668 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 669 | { 670 | self 671 | } 672 | } 673 | 674 | impl Source for Failure { 675 | fn new(_: T) -> Self { 676 | Self 677 | } 678 | } 679 | 680 | #[cfg(feature = "alloc")] 681 | pub use boxed_error::BoxedError; 682 | 683 | #[cfg(all(debug_assertions, feature = "alloc"))] 684 | type ErrorType = BoxedError; 685 | #[cfg(not(all(debug_assertions, feature = "alloc")))] 686 | type ErrorType = Failure; 687 | 688 | /// A good general-purpose error type. 689 | /// 690 | /// If `debug_assertions` and the `alloc` feature are enabled, then this error 691 | /// will have the same behavior as [`BoxedError`]. Otherwise, it will behave 692 | /// like [`Failure`]. 693 | #[derive(Debug)] 694 | pub struct Error { 695 | inner: ErrorType, 696 | } 697 | 698 | impl fmt::Display for Error { 699 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 700 | write!(f, "{}", self.inner)?; 701 | #[cfg(not(all(debug_assertions, feature = "alloc")))] 702 | write!( 703 | f, 704 | "; enable debug assertions and the `alloc` feature in rancor for \ 705 | error information" 706 | )?; 707 | 708 | Ok(()) 709 | } 710 | } 711 | 712 | impl error::Error for Error { 713 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 714 | self.inner.source() 715 | } 716 | } 717 | 718 | impl Trace for Error { 719 | fn trace(self, trace: R) -> Self 720 | where 721 | R: fmt::Debug + fmt::Display + Send + Sync + 'static, 722 | { 723 | Self { 724 | inner: self.inner.trace(trace), 725 | } 726 | } 727 | } 728 | 729 | impl Source for Error { 730 | fn new(source: T) -> Self { 731 | Self { 732 | inner: ErrorType::new(source), 733 | } 734 | } 735 | } 736 | 737 | #[cfg(test)] 738 | mod test { 739 | use super::*; 740 | 741 | struct Inner { 742 | value: u64, 743 | } 744 | 745 | #[test] 746 | fn test_strategy() { 747 | let mut inner = Inner { value: 10 }; 748 | let address = &inner.value as *const u64; 749 | let strategy: &mut Strategy = 750 | Strategy::wrap(&mut inner); 751 | let s_address = (&strategy.inner.value) as *const u64; 752 | assert_eq!(address, s_address); 753 | 754 | assert_eq!(strategy.value, 10); 755 | strategy.value = 20; 756 | assert_eq!(strategy.value, 20); 757 | assert_eq!(inner.value, 20); 758 | } 759 | } 760 | -------------------------------------------------------------------------------- /src/thin_box.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | alloc::Layout, 3 | fmt, 4 | marker::PhantomData, 5 | mem::size_of, 6 | ops::{Deref, DerefMut}, 7 | ptr::NonNull, 8 | }; 9 | 10 | use ptr_meta::Pointee; 11 | 12 | use crate::alloc::alloc::{alloc, dealloc}; 13 | 14 | pub struct ThinBox { 15 | ptr: NonNull<()>, 16 | _phantom: PhantomData, 17 | } 18 | 19 | // SAFETY: `ThinBox` owns the value it points to, so it is `Send` if `T` is also 20 | // `Send`. 21 | unsafe impl Send for ThinBox {} 22 | 23 | // SAFETY: `ThinBox` owns the value it points to, so it is `Sync` if `T` is also 24 | // `Sync`. 25 | unsafe impl Sync for ThinBox {} 26 | 27 | impl Drop for ThinBox { 28 | fn drop(&mut self) { 29 | let ptr = self.as_ptr(); 30 | // SAFETY: `ptr` always points to a valid `T`, even when it's dangling. 31 | let value = unsafe { &*ptr }; 32 | let value_layout = Layout::for_value(value); 33 | // SAFETY: `ptr` is always initialized and we own it, so we may drop it. 34 | // We only ever drop it during `drop`, so it won't get dropped twice. 35 | unsafe { 36 | self.as_ptr().drop_in_place(); 37 | } 38 | let (layout, header) = Self::layout_for(value_layout); 39 | if layout.size() > 0 { 40 | // SAFETY: The pointer passed to `dealloc` is our raw pointer moved 41 | // backwards to the beginning of the allocation. `layout` is the 42 | // same layout used to allocate the memory because it is from 43 | // `Self::layout_for` given the layout of the owned value. 44 | unsafe { 45 | dealloc(ptr.cast::().sub(header), layout); 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl fmt::Debug for ThinBox { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | self.deref().fmt(f) 54 | } 55 | } 56 | 57 | impl fmt::Display for ThinBox { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | self.deref().fmt(f) 60 | } 61 | } 62 | 63 | impl ThinBox { 64 | fn layout_for(value_layout: Layout) -> (Layout, usize) { 65 | let meta_layout = Layout::new::(); 66 | if meta_layout.size() == 0 { 67 | (value_layout, 0) 68 | } else { 69 | let align = usize::max(value_layout.align(), meta_layout.align()); 70 | let header = usize::max(align, meta_layout.size()); 71 | let size = value_layout.size() + header; 72 | let layout = Layout::from_size_align(size, align).unwrap(); 73 | (layout, header) 74 | } 75 | } 76 | 77 | /// # Safety 78 | /// 79 | /// `cast` must return the same pointer _unsized_ to a pointer to `T`. 80 | pub unsafe fn new_unchecked(value: U, cast: F) -> Self 81 | where 82 | F: FnOnce(*mut U) -> *mut T, 83 | { 84 | let (layout, header) = Self::layout_for(Layout::new::()); 85 | if layout.size() == 0 { 86 | Self { 87 | ptr: NonNull::dangling(), 88 | _phantom: PhantomData, 89 | } 90 | } else { 91 | // SAFETY: We checked that `layout` has non-zero size. 92 | let raw_ptr = unsafe { NonNull::new(alloc(layout)).unwrap() }; 93 | // SAFETY: `layout_for` returns a layout that is aligned for and has 94 | // space for `value` after the first `header` bytes. Adding `header` 95 | // bytes to `raw_ptr` will always be in-bounds. 96 | let value_ptr = unsafe { raw_ptr.as_ptr().add(header).cast::() }; 97 | // SAFETY: `value_ptr` points to a memory location suitable for 98 | // `value`. 99 | unsafe { 100 | value_ptr.write(value); 101 | } 102 | let ptr = cast(value_ptr); 103 | // SAFETY: The metadata for the thin box is always located right 104 | // before the end of the header, so offsetting part-way into the 105 | // header will always be in-bounds. 106 | let meta_ptr = unsafe { 107 | raw_ptr 108 | .as_ptr() 109 | .add(header - size_of::()) 110 | .cast::() 111 | }; 112 | // SAFETY: `meta_ptr` points to memory properly aligned for the 113 | // metadata of `T`. 114 | unsafe { 115 | meta_ptr.write(ptr_meta::metadata(ptr)); 116 | } 117 | Self { 118 | // SAFETY: `value_ptr` is offset from `raw_ptr`, which we made 119 | // sure was not null. 120 | ptr: unsafe { NonNull::new_unchecked(ptr.cast()) }, 121 | _phantom: PhantomData, 122 | } 123 | } 124 | } 125 | 126 | pub fn as_ptr(&self) -> *mut T { 127 | let data_address = self.ptr.as_ptr(); 128 | // SAFETY: The metadata for the value is held immediately before the 129 | // address the pointer points to and it always initialized, even when 130 | // `T` is a ZST with metadata. 131 | let metadata = unsafe { *data_address.cast::().sub(1) }; 132 | ptr_meta::from_raw_parts_mut(data_address, metadata) 133 | } 134 | } 135 | 136 | impl Deref for ThinBox { 137 | type Target = T; 138 | 139 | fn deref(&self) -> &Self::Target { 140 | // SAFETY: `ThinBox` always points to a valid `T`. 141 | unsafe { &*self.as_ptr().cast_const() } 142 | } 143 | } 144 | 145 | impl DerefMut for ThinBox { 146 | fn deref_mut(&mut self) -> &mut Self::Target { 147 | // SAFETY: `ThinBox` always points to a valid `T`. 148 | unsafe { &mut *self.as_ptr() } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use crate::{ 155 | alloc::string::{String, ToString}, 156 | thin_box::ThinBox, 157 | }; 158 | 159 | #[ptr_meta::pointee] 160 | trait DynTrait { 161 | fn int(&self) -> i32; 162 | } 163 | 164 | impl DynTrait for () { 165 | fn int(&self) -> i32 { 166 | 10 167 | } 168 | } 169 | 170 | impl DynTrait for i32 { 171 | fn int(&self) -> i32 { 172 | *self 173 | } 174 | } 175 | 176 | impl DynTrait for String { 177 | fn int(&self) -> i32 { 178 | self.parse().unwrap() 179 | } 180 | } 181 | 182 | #[test] 183 | fn sized_types() { 184 | let box_unit = unsafe { ThinBox::new_unchecked((), |x| x) }; 185 | assert_eq!(*box_unit, ()); 186 | 187 | let box_int = unsafe { ThinBox::new_unchecked(10, |x| x) }; 188 | assert_eq!(*box_int, 10); 189 | 190 | let box_string = 191 | unsafe { ThinBox::new_unchecked("hello world".to_string(), |x| x) }; 192 | assert_eq!(*box_string, "hello world"); 193 | } 194 | 195 | #[test] 196 | fn unsized_types() { 197 | let box_dyn_int = 198 | unsafe { ThinBox::new_unchecked(10, |x| x as *mut dyn DynTrait) }; 199 | assert_eq!(box_dyn_int.int(), 10); 200 | 201 | let box_dyn_string = unsafe { 202 | ThinBox::new_unchecked("10".to_string(), |x| x as *mut dyn DynTrait) 203 | }; 204 | assert_eq!(box_dyn_string.int(), 10); 205 | 206 | let box_slice = unsafe { 207 | ThinBox::new_unchecked([1, 2, 3, 4], |x| x as *mut [i32]) 208 | }; 209 | assert_eq!(*box_slice, [1, 2, 3, 4]); 210 | } 211 | 212 | #[test] 213 | fn zst_dst() { 214 | let box_unit_debug = 215 | unsafe { ThinBox::new_unchecked((), |x| x as *mut dyn DynTrait) }; 216 | assert_eq!(box_unit_debug.int(), 10); 217 | 218 | let box_empty_slice = 219 | unsafe { ThinBox::new_unchecked([], |x| x as *mut [i32]) }; 220 | assert_eq!(*box_empty_slice, []); 221 | } 222 | } 223 | --------------------------------------------------------------------------------