rancor-0.1.0/.cargo/config.toml000064400000000000000000000000661046102023000144230ustar 00000000000000[build] rustdocflags = ["--cfg", "docsrs"] [env] rancor-0.1.0/.cargo_vcs_info.json0000644000000001360000000000100123160ustar { "git": { "sha1": "ea7e969efe1dce4b338c81d5b756fd01e7912040" }, "path_in_vcs": "" }rancor-0.1.0/.github/FUNDING.yml000064400000000000000000000000161046102023000142600ustar 00000000000000github: rkyv rancor-0.1.0/.github/workflows/ci.yml000064400000000000000000000063741046102023000156330ustar 00000000000000name: CI on: push: pull_request: workflow_dispatch: schedule: - cron: "0 10 * * *" permissions: contents: read env: RUSTFLAGS: -Dwarnings jobs: features: name: Features / ${{ matrix.alloc }} ${{ matrix.derive }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: alloc: - '' - alloc steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test --verbose --tests --no-default-features --features "${{ matrix.std }}" toolchain: name: Toolchain / ${{ matrix.toolchain }} ${{ matrix.opt }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: toolchain: - stable - beta - nightly opt: - '' - --release steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - run: cargo test --verbose ${{ matrix.opt }} miri: name: Miri / ${{ matrix.opt }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: opt: - '' - --release steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@miri - run: cargo miri setup - run: cargo miri test ${{ matrix.opt }} --verbose env: MIRIFLAGS: -Zmiri-disable-stacked-borrows -Zmiri-tree-borrows test: name: Test / ${{ matrix.target }} ${{ matrix.opt }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: opt: - '' - --release include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest target: aarch64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test ${{ matrix.opt }} cross: name: Cross / ${{ matrix.target }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: target: - i686-unknown-linux-gnu - i586-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu - thumbv6m-none-eabi steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cross - run: cross build --no-default-features --features "alloc" --target ${{ matrix.target }} --verbose format: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: rustfmt - run: cargo fmt --check clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: clippy - run: cargo clippy doc: name: Doc runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - run: cargo doc rancor-0.1.0/.gitignore000064400000000000000000000000241046102023000130720ustar 00000000000000/target /Cargo.lock rancor-0.1.0/Cargo.toml0000644000000022370000000000100103200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.81" name = "rancor" version = "0.1.0" authors = ["David Koloski "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Scalable and efficient error handling without type composition" documentation = "https://docs.rs/rancor" readme = "README.md" keywords = [ "error", "error-handling", "no_std", ] categories = [ "rust-patterns", "no-std", ] license = "MIT" repository = "https://github.com/rkyv/rancor" [lib] name = "rancor" path = "src/lib.rs" [dependencies.ptr_meta] version = "0.3" features = ["derive"] default-features = false [features] alloc = [] default = ["alloc"] rancor-0.1.0/Cargo.toml.orig000064400000000000000000000012001046102023000137660ustar 00000000000000[package] name = "rancor" description = "Scalable and efficient error handling without type composition" version = "0.1.0" authors = ["David Koloski "] edition = "2021" rust-version = "1.81" license = "MIT" readme = "README.md" repository = "https://github.com/rkyv/rancor" keywords = ["error", "error-handling", "no_std"] categories = ["rust-patterns", "no-std"] documentation = "https://docs.rs/rancor" [dependencies] ptr_meta = { version = "0.3", default-features = false, features = ["derive"] } [features] default = ["alloc"] alloc = [] [patch.crates-io] ptr_meta = { git = "https://github.com/rkyv/ptr_meta" } rancor-0.1.0/LICENSE000064400000000000000000000020441046102023000121130ustar 00000000000000Copyright 2023 David Koloski 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. rancor-0.1.0/README.md000064400000000000000000000011571046102023000123710ustar 00000000000000# `rancor` [![crates.io badge]][crates.io] [![docs badge]][docs] [![license badge]][license] [crates.io badge]: https://img.shields.io/crates/v/rancor.svg [crates.io]: https://crates.io/crates/rancor [docs badge]: https://img.shields.io/docsrs/rancor [docs]: https://docs.rs/rancor [license badge]: https://img.shields.io/badge/license-MIT-blue.svg [license]: https://github.com/rkyv/rancor/blob/master/LICENSE Rancor provides scalable and efficient error handling without using type composition. ## Documentation - [rancor](https://docs.rs/rancor), a scalable and efficient error handling library rancor-0.1.0/rustfmt.toml000064400000000000000000000006511046102023000135110ustar 00000000000000max_width = 80 comment_width = 80 wrap_comments = true group_imports = "StdExternalCrate" imports_granularity = "Crate" condense_wildcard_suffixes = true error_on_line_overflow = true error_on_unformatted = true format_code_in_doc_comments = true format_macro_matchers = true format_macro_bodies = true format_strings = true hex_literal_case = "Lower" normalize_comments = true use_field_init_shorthand = true rancor-0.1.0/src/boxed_error.rs000064400000000000000000000037551046102023000145670ustar 00000000000000use core::{error, fmt}; use crate::{thin_box::ThinBox, Source, Trace}; #[ptr_meta::pointee] trait ErrorTrace: fmt::Debug + fmt::Display + Send + Sync + 'static {} impl ErrorTrace for T where T: fmt::Debug + fmt::Display + Send + Sync + 'static + ?Sized { } #[derive(Debug)] struct ErrorWithTrace { error: BoxedError, trace: ThinBox, } impl fmt::Display for ErrorWithTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.error)?; write!(f, "trace: {}", self.trace)?; Ok(()) } } impl error::Error for ErrorWithTrace { fn source(&self) -> Option<&(dyn error::Error + 'static)> { self.error.inner.source() } } /// An error type that preserves all detailed error messages. It is optimized /// to fit in a single pointer. #[derive(Debug)] pub struct BoxedError { inner: ThinBox, } impl fmt::Display for BoxedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inner) } } impl error::Error for BoxedError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { self.inner.source() } } impl Trace for BoxedError { fn trace(self, trace: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static, { Self::new(ErrorWithTrace { error: self, // SAFETY: The provided closure returns the same pointer unsized to // a `dyn ErrorTrace`. trace: unsafe { ThinBox::new_unchecked(trace, |ptr| ptr as *mut _) }, }) } } impl Source for BoxedError { fn new(source: T) -> Self { Self { // SAFETY: The provided closure returns the same pointer unsized to // a `dyn Error`. inner: unsafe { ThinBox::new_unchecked(source, |ptr| ptr as *mut _) }, } } } rancor-0.1.0/src/lib.rs000064400000000000000000000474331046102023000130240ustar 00000000000000//! # rancor //! //! rancor provides scalable and efficient error handling without using type //! composition. This makes it best-suited for situations where: //! //! - Programmatic error introspection is not useful //! - Functions may error, but succeed most of the time //! - Errors should provide as much useful detail as possible when emitted //! - Use cases include both `no_std` and targets with support for `std` //! //! ## Features //! //! - `alloc`: Provides the [`BoxedError`] type. Enabled by default. #![deny( future_incompatible, missing_docs, nonstandard_style, unsafe_op_in_unsafe_fn, unused, warnings, clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks, rustdoc::broken_intra_doc_links, rustdoc::missing_crate_level_docs )] #![no_std] #![cfg_attr(all(docsrs, not(doctest)), feature(doc_cfg, doc_auto_cfg))] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] mod boxed_error; #[cfg(feature = "alloc")] mod thin_box; use core::{ error, fmt, hint::unreachable_unchecked, marker::PhantomData, ops::{Deref, DerefMut}, }; /// An error type which can add additional "trace" information to itself. /// /// Some functions only add additional context to errors created by other /// functions, rather than creating errors themselves. With generics, it's /// therefore possible to have a generic function which can produce errors with /// some type arguments but not with others. In these cases, `Trace` allows /// those functions to add context if an error can occur, and compile out the /// context if the error type is [`Infallible`] or [`Panic`]. /// /// # Example /// /// ``` /// use rancor::{ResultExt, Trace}; /// /// trait Print { /// fn print(&self, message: &str) -> Result<(), E>; /// } /// /// fn print_hello_world, E: Trace>(printer: &T) -> Result<(), E> { /// printer.print("hello").trace("failed to print hello")?; /// printer.print("world").trace("failed to print world")?; /// Ok(()) /// } /// ``` pub trait Trace: Sized + Send + Sync + 'static { /// Adds an additional trace to this error, returning a new error. fn trace(self, trace: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static; } /// An error type which can be uniformly constructed from an [`Error`] and /// additional trace information. /// /// # Example /// /// ``` /// use core::{error::Error, fmt}; /// /// use rancor::{fail, Source}; /// /// #[derive(Debug)] /// struct DivideByZeroError; /// /// impl fmt::Display for DivideByZeroError { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// write!(f, "attempted to divide by zero") /// } /// } /// /// impl Error for DivideByZeroError {} /// /// fn try_divide(a: i32, b: i32) -> Result { /// if b == 0 { /// fail!(DivideByZeroError); /// } /// Ok(a / b) /// } /// ``` pub trait Source: Trace + error::Error { /// Returns a new `Self` using the given [`Error`]. /// /// Depending on the specific implementation, this may box the error, /// immediately emit a diagnostic, or discard it and only remember that some /// error occurred. fn new(source: T) -> Self; } /// A type with fallible operations that return its associated error type. /// /// `Fallible` turns an error type parameter into an associated type of another /// parameter. You can equip an existing type with a `Fallible` implementation /// by wrapping it in a [`Strategy`]. /// /// # Example /// /// ``` /// use rancor::{Failure, Fallible, Strategy}; /// /// trait Operator::Error> { /// fn operate(&self, lhs: i32, rhs: i32) -> Result; /// } /// /// impl + ?Sized, E> Operator for Strategy { /// fn operate(&self, lhs: i32, rhs: i32) -> Result { /// T::operate(self, lhs, rhs) /// } /// } /// /// struct Add; /// /// impl Operator for Add { /// fn operate(&self, lhs: i32, rhs: i32) -> Result { /// Ok(lhs + rhs) /// } /// } /// /// fn operate_one_one( /// operator: &T, /// ) -> Result { /// operator.operate(1, 1) /// } /// /// assert_eq!( /// operate_one_one(Strategy::<_, Failure>::wrap(&mut Add)), /// Ok(2) /// ); /// ``` pub trait Fallible { /// The error type associated with this type's operations. type Error; } /// Equips a type with a `Fallible` implementation that chooses `E` as its error /// type. /// /// # Example /// /// ``` /// use rancor::{Failure, Fallible, Strategy}; /// /// trait Print::Error> { /// fn print(&self, message: &str) -> Result<(), E>; /// } /// /// impl + ?Sized, E> Print for Strategy { /// fn print(&self, message: &str) -> Result<(), E> { /// T::print(self, message) /// } /// } /// /// struct StdOut; /// /// impl Print for StdOut { /// fn print(&self, message: &str) -> Result<(), E> { /// println!("{message}"); /// Ok(()) /// } /// } /// /// Strategy::<_, Failure>::wrap(&mut StdOut) /// .print("hello world") /// .unwrap(); /// ``` #[repr(transparent)] pub struct Strategy { _error: PhantomData, inner: T, } impl Fallible for Strategy { type Error = E; } impl Strategy { /// Wraps the given mutable reference, returning a mutable reference to a /// `Strategy`. /// /// ## Example /// ``` /// use core::ops::Deref; /// /// use rancor::{Failure, Strategy}; /// fn test() { /// struct Inner { /// value: u64, /// } /// /// let mut inner = Inner { value: 10 }; /// /// let inner_value_ptr = &inner.value as *const u64; /// let strategy: &mut Strategy = /// Strategy::wrap(&mut inner); /// let strategy_value_ptr = (&strategy.deref().value) as *const u64; /// assert_eq!(inner_value_ptr, strategy_value_ptr); /// // Strategy wraps a type but does not change its memory layout. /// } /// /// test(); /// ``` pub fn wrap(inner: &mut T) -> &mut Self { // SAFETY: `Strategy` is `repr(transparent)` and so has the same layout // as `T`. The input and output lifetimes are the same, so mutable // aliasing rules will be upheld. Finally, because the inner `T` is the // final element of `Strategy`, the pointer metadata of the two pointers // will be the same. unsafe { core::mem::transmute::<&mut T, &mut Self>(inner) } } } impl Deref for Strategy { type Target = T; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for Strategy { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } /// Returns the given error from this function. /// /// The current function must return a `Result<_, E>` where `E` implements /// [`Source`]. /// /// # Example /// /// ``` /// use core::{error::Error, fmt}; /// /// use rancor::{fail, Source}; /// /// #[derive(Debug)] /// struct DivideByZeroError; /// /// impl fmt::Display for DivideByZeroError { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// write!(f, "attempted to divide by zero") /// } /// } /// /// impl Error for DivideByZeroError {} /// /// fn divide(a: i32, b: i32) -> Result { /// if b == 0 { /// fail!(DivideByZeroError); /// } /// Ok(a / b) /// } /// ``` #[macro_export] macro_rules! fail { ($($x:tt)*) => { return ::core::result::Result::Err($crate::Source::new($($x)*)); }; } /// Helper methods for `Result`s. pub trait ResultExt { /// Returns a `Result` with this error type converted to `U`. /// /// # Example /// /// ``` /// use rancor::{Failure, ResultExt as _}; /// /// let result = "1_000".parse::().into_error::(); /// ``` fn into_error(self) -> Result where U: Source, E: error::Error + Send + Sync + 'static; /// Returns a `Result` with this error type converted to `U` and with an /// additional `trace` message added. /// /// # Example /// /// ``` /// use rancor::{BoxedError, ResultExt as _}; /// /// let result = "1_000" /// .parse::() /// .into_trace::("while parsing 1_000"); /// ``` fn into_trace(self, trace: R) -> Result where U: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, E: error::Error + Send + Sync + 'static; /// Returns a `Result` with this error type converted to `U` and with an /// additional trace message added by evaluating the given function `f`. The /// function is evaluated only if an error occurred. /// /// # Example /// /// ``` /// use rancor::{BoxedError, ResultExt as _}; /// /// let input = "1_000"; /// let result = /// input /// .parse::() /// .into_with_trace::(|| { /// format!("while parsing {input}") /// }); /// ``` fn into_with_trace(self, f: F) -> Result where U: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R, E: error::Error + Send + Sync + 'static; /// Adds an additional `trace` message to the error value of this type. /// /// # Example /// /// ``` /// use rancor::{BoxedError, ResultExt as _}; /// /// let result = "1_000" /// .parse::() /// .into_error::() /// .trace("while parsing 1_000"); /// ``` fn trace(self, trace: R) -> Result where R: fmt::Debug + fmt::Display + Send + Sync + 'static, E: Trace; /// Adds an additional trace message to the error value of this type by /// evaluating the given function `f`. The function is evaluated only if an /// error occurred. /// /// # Example /// /// ``` /// use rancor::{BoxedError, ResultExt as _}; /// /// let input = "1_000"; /// let result = input /// .parse::() /// .into_error::() /// .with_trace(|| format!("while parsing {input}")); /// ``` fn with_trace(self, f: F) -> Result where R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R, E: Trace; /// Safely unwraps a result that is always `Ok`. /// /// In order to call this method, the error type of this `Result` must be a /// [`Never`] type. /// /// # Example /// /// ``` /// use rancor::{Infallible, ResultExt}; /// /// let inner = Ok::(10).always_ok(); /// ``` fn always_ok(self) -> T where E: Never; } impl ResultExt for Result { fn into_error(self) -> Result where U: Source, E: error::Error + Send + Sync + 'static, { match self { Ok(x) => Ok(x), Err(e) => Err(U::new(e)), } } fn into_trace(self, trace: R) -> Result where U: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, E: error::Error + Send + Sync + 'static, { match self { Ok(x) => Ok(x), Err(e) => Err(U::new(e).trace(trace)), } } fn into_with_trace(self, f: F) -> Result where U: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R, E: error::Error + Send + Sync + 'static, { match self { Ok(x) => Ok(x), Err(e) => Err(U::new(e).trace(f())), } } fn trace(self, trace: R) -> Result where R: fmt::Debug + fmt::Display + Send + Sync + 'static, E: Trace, { match self { Ok(x) => Ok(x), Err(e) => Err(e.trace(trace)), } } fn with_trace(self, f: F) -> Result where R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R, E: Trace, { match self { Ok(x) => Ok(x), Err(e) => Err(e.trace(f())), } } fn always_ok(self) -> T where E: Never, { match self { Ok(x) => x, Err(e) => unreachable_checked(e), } } } /// Helper methods for `Option`s. pub trait OptionExt { /// Returns a `Result` with an error indicating that `Some` was expected but /// `None` was found. /// /// # Example /// /// ``` /// use rancor::{Failure, OptionExt}; /// /// let result = Some(10).into_error::(); /// ``` fn into_error(self) -> Result where E: Source; /// Returns a `Result` with an error indicating that `Some` was expected but /// `None` was found, and with an additional `trace` message added. /// /// # Example /// /// ``` /// use rancor::{Failure, OptionExt}; /// /// ##[rustfmt::skip] /// let result = Some(10). /// into_trace::("while converting Some(10)"); /// ``` fn into_trace(self, trace: R) -> Result where E: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static; /// Returns a `Result` with an error indicating that `Some` was expected but /// `None` was found, and with an additional trace message added by /// evaluating the given function `f`. The function is evaluated only if an /// error occurred. /// /// # Example /// /// ``` /// use rancor::{Failure, OptionExt}; /// /// let input = Some(10); /// let result = input.into_with_trace::(|| { /// format!("while converting {input:?}") /// }); /// ``` fn into_with_trace(self, f: F) -> Result where E: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R; } /// A type that can never be produced. /// /// Never types include the unstable `!` type, enums with no variants, or any /// type that always contains a never type (e.g. a struct with a `Never` field). /// /// # Safety /// /// It must be impossible to produce a value of this type. pub unsafe trait Never {} /// Consumes a `Never` type, returning a primitive `!`. /// /// This is a safe version of [`unreachable_unchecked`] for `Never` types. /// /// # Example /// /// ``` /// use rancor::{unreachable_checked, Infallible}; /// /// let result = Ok::(10); /// match result { /// Ok(i) => println!("i"), /// Err(e) => unreachable_checked(e), /// } /// ``` #[inline(always)] pub const fn unreachable_checked(_: T) -> ! { // SAFETY: Types that implement `Never` cannot be constructed, // so this is unreachable. unsafe { unreachable_unchecked() } } #[derive(Debug)] struct NoneError; impl fmt::Display for NoneError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "`Option` is `None`, expected `Some`") } } impl error::Error for NoneError {} impl OptionExt for Option { fn into_error(self) -> Result where E: Source, { match self { Some(x) => Ok(x), None => Err(E::new(NoneError)), } } fn into_trace(self, trace: R) -> Result where E: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, { match self { Some(x) => Ok(x), None => Err(E::new(NoneError).trace(trace)), } } fn into_with_trace(self, f: F) -> Result where E: Source, R: fmt::Debug + fmt::Display + Send + Sync + 'static, F: FnOnce() -> R, { match self { Some(x) => Ok(x), None => Err(E::new(NoneError).trace(f())), } } } /// A re-export of `core::convert::Infallible`. pub use core::convert::Infallible; // SAFETY: `Infallible` is an enum with no variants, and so cannot be produced. unsafe impl Never for Infallible {} impl Trace for Infallible { fn trace(self, _: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static, { match self {} } } /// An error type that does not occupy any space, panicking on creation instead. /// /// Because panicking occurs immediately upon creation, this error type will not /// print any additional trace information. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Panic {} // SAFETY: `Panic` is an enum with no variants, and so cannot be produced. unsafe impl Never for Panic {} impl fmt::Display for Panic { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { match *self {} } } impl error::Error for Panic {} impl Trace for Panic { fn trace(self, _: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static, { match self {} } } impl Source for Panic { fn new(error: T) -> Self { panic!("created a new `Panic` from: {error}"); } } /// An error type that only preserves success or failure, throwing away any more /// detailed error messages. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Failure; impl fmt::Display for Failure { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "failed without error information") } } impl error::Error for Failure {} impl Trace for Failure { fn trace(self, _: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static, { self } } impl Source for Failure { fn new(_: T) -> Self { Self } } #[cfg(feature = "alloc")] pub use boxed_error::BoxedError; #[cfg(all(debug_assertions, feature = "alloc"))] type ErrorType = BoxedError; #[cfg(not(all(debug_assertions, feature = "alloc")))] type ErrorType = Failure; /// A good general-purpose error type. /// /// If `debug_assertions` and the `alloc` feature are enabled, then this error /// will have the same behavior as [`BoxedError`]. Otherwise, it will behave /// like [`Failure`]. #[derive(Debug)] pub struct Error { inner: ErrorType, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inner)?; #[cfg(not(all(debug_assertions, feature = "alloc")))] write!( f, "; enable debug assertions and the `alloc` feature in rancor for \ error information" )?; Ok(()) } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { self.inner.source() } } impl Trace for Error { fn trace(self, trace: R) -> Self where R: fmt::Debug + fmt::Display + Send + Sync + 'static, { Self { inner: self.inner.trace(trace), } } } impl Source for Error { fn new(source: T) -> Self { Self { inner: ErrorType::new(source), } } } #[cfg(test)] mod test { use super::*; struct Inner { value: u64, } #[test] fn test_strategy() { let mut inner = Inner { value: 10 }; let address = &inner.value as *const u64; let strategy: &mut Strategy = Strategy::wrap(&mut inner); let s_address = (&strategy.inner.value) as *const u64; assert_eq!(address, s_address); assert_eq!(strategy.value, 10); strategy.value = 20; assert_eq!(strategy.value, 20); assert_eq!(inner.value, 20); } } rancor-0.1.0/src/thin_box.rs000064400000000000000000000157531046102023000140700ustar 00000000000000use core::{ alloc::Layout, fmt, marker::PhantomData, mem::size_of, ops::{Deref, DerefMut}, ptr::NonNull, }; use ptr_meta::Pointee; use crate::alloc::alloc::{alloc, dealloc}; pub struct ThinBox { ptr: NonNull<()>, _phantom: PhantomData, } // SAFETY: `ThinBox` owns the value it points to, so it is `Send` if `T` is also // `Send`. unsafe impl Send for ThinBox {} // SAFETY: `ThinBox` owns the value it points to, so it is `Sync` if `T` is also // `Sync`. unsafe impl Sync for ThinBox {} impl Drop for ThinBox { fn drop(&mut self) { let ptr = self.as_ptr(); // SAFETY: `ptr` always points to a valid `T`, even when it's dangling. let value = unsafe { &*ptr }; let value_layout = Layout::for_value(value); // SAFETY: `ptr` is always initialized and we own it, so we may drop it. // We only ever drop it during `drop`, so it won't get dropped twice. unsafe { self.as_ptr().drop_in_place(); } let (layout, header) = Self::layout_for(value_layout); if layout.size() > 0 { // SAFETY: The pointer passed to `dealloc` is our raw pointer moved // backwards to the beginning of the allocation. `layout` is the // same layout used to allocate the memory because it is from // `Self::layout_for` given the layout of the owned value. unsafe { dealloc(ptr.cast::().sub(header), layout); } } } } impl fmt::Debug for ThinBox { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) } } impl fmt::Display for ThinBox { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) } } impl ThinBox { fn layout_for(value_layout: Layout) -> (Layout, usize) { let meta_layout = Layout::new::(); if meta_layout.size() == 0 { (value_layout, 0) } else { let align = usize::max(value_layout.align(), meta_layout.align()); let header = usize::max(align, meta_layout.size()); let size = value_layout.size() + header; let layout = Layout::from_size_align(size, align).unwrap(); (layout, header) } } /// # Safety /// /// `cast` must return the same pointer _unsized_ to a pointer to `T`. pub unsafe fn new_unchecked(value: U, cast: F) -> Self where F: FnOnce(*mut U) -> *mut T, { let (layout, header) = Self::layout_for(Layout::new::()); if layout.size() == 0 { Self { ptr: NonNull::dangling(), _phantom: PhantomData, } } else { // SAFETY: We checked that `layout` has non-zero size. let raw_ptr = unsafe { NonNull::new(alloc(layout)).unwrap() }; // SAFETY: `layout_for` returns a layout that is aligned for and has // space for `value` after the first `header` bytes. Adding `header` // bytes to `raw_ptr` will always be in-bounds. let value_ptr = unsafe { raw_ptr.as_ptr().add(header).cast::() }; // SAFETY: `value_ptr` points to a memory location suitable for // `value`. unsafe { value_ptr.write(value); } let ptr = cast(value_ptr); // SAFETY: The metadata for the thin box is always located right // before the end of the header, so offsetting part-way into the // header will always be in-bounds. let meta_ptr = unsafe { raw_ptr .as_ptr() .add(header - size_of::()) .cast::() }; // SAFETY: `meta_ptr` points to memory properly aligned for the // metadata of `T`. unsafe { meta_ptr.write(ptr_meta::metadata(ptr)); } Self { // SAFETY: `value_ptr` is offset from `raw_ptr`, which we made // sure was not null. ptr: unsafe { NonNull::new_unchecked(ptr.cast()) }, _phantom: PhantomData, } } } pub fn as_ptr(&self) -> *mut T { let data_address = self.ptr.as_ptr(); // SAFETY: The metadata for the value is held immediately before the // address the pointer points to and it always initialized, even when // `T` is a ZST with metadata. let metadata = unsafe { *data_address.cast::().sub(1) }; ptr_meta::from_raw_parts_mut(data_address, metadata) } } impl Deref for ThinBox { type Target = T; fn deref(&self) -> &Self::Target { // SAFETY: `ThinBox` always points to a valid `T`. unsafe { &*self.as_ptr().cast_const() } } } impl DerefMut for ThinBox { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: `ThinBox` always points to a valid `T`. unsafe { &mut *self.as_ptr() } } } #[cfg(test)] mod tests { use crate::{ alloc::string::{String, ToString}, thin_box::ThinBox, }; #[ptr_meta::pointee] trait DynTrait { fn int(&self) -> i32; } impl DynTrait for () { fn int(&self) -> i32 { 10 } } impl DynTrait for i32 { fn int(&self) -> i32 { *self } } impl DynTrait for String { fn int(&self) -> i32 { self.parse().unwrap() } } #[test] fn sized_types() { let box_unit = unsafe { ThinBox::new_unchecked((), |x| x) }; assert_eq!(*box_unit, ()); let box_int = unsafe { ThinBox::new_unchecked(10, |x| x) }; assert_eq!(*box_int, 10); let box_string = unsafe { ThinBox::new_unchecked("hello world".to_string(), |x| x) }; assert_eq!(*box_string, "hello world"); } #[test] fn unsized_types() { let box_dyn_int = unsafe { ThinBox::new_unchecked(10, |x| x as *mut dyn DynTrait) }; assert_eq!(box_dyn_int.int(), 10); let box_dyn_string = unsafe { ThinBox::new_unchecked("10".to_string(), |x| x as *mut dyn DynTrait) }; assert_eq!(box_dyn_string.int(), 10); let box_slice = unsafe { ThinBox::new_unchecked([1, 2, 3, 4], |x| x as *mut [i32]) }; assert_eq!(*box_slice, [1, 2, 3, 4]); } #[test] fn zst_dst() { let box_unit_debug = unsafe { ThinBox::new_unchecked((), |x| x as *mut dyn DynTrait) }; assert_eq!(box_unit_debug.int(), 10); let box_empty_slice = unsafe { ThinBox::new_unchecked([], |x| x as *mut [i32]) }; assert_eq!(*box_empty_slice, []); } }