diff --git a/.travis.yml b/.travis.yml index 03219c8..da7b5a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,11 @@ before_script: | script: | ( set -o errexit;set -o pipefail; set -o xtrace;set -o nounset; for RUST_TOOLCHAIN in $RUST_TOOLCHAINS; do ( + cargo +$RUST_TOOLCHAIN test cargo +$RUST_TOOLCHAIN test --all-features + cargo +$RUST_TOOLCHAIN test --no-default-features ); done + cargo +$FMT_VERSION fmt -- --check cargo +$CLIPPY_VERSION clippy --all-targets --all-features -- -D warnings cargo +$DOCS_RS_VERSION doc --no-deps --all-features diff --git a/Cargo.toml b/Cargo.toml index 0482fe7..97d5c1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,9 @@ readme = "README.md" [badges] travis-ci = { repository = "alecmocatta/replace_with" } maintenance = { status = "actively-developed" } + +[features] +default = ["std"] +std = [] +nightly = [] +panic_abort = [] diff --git a/README.md b/README.md index fbaafc0..a81120c 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,56 @@ impl States { Huzzah! +## `no_std` + +To use `replace_with` with `no_std` you have to disable the `std` feature, which is active by default, by specifying your dependency to it like this: + +```toml +# Cargo.toml + +[dependencies.replace_with] +version = ... +default-features = false +features = [] +... +``` + +The [`replace_with()`](https://docs.rs/replace_with/0.1.2/replace_with/fn.replace_with.html) & [`replace_with_or_default()`](https://docs.rs/replace_with/0.1.2/replace_with/fn.replace_with_or_default.html) functions are available on stable Rust both, with and without `std`. + +The [`replace_with_or_abort()`](https://docs.rs/replace_with/0.1.2/replace_with/fn.replace_with_or_abort.html) function however by default makes use of [`std::process::abort()`](https://doc.rust-lang.org/std/process/fn.abort.html) which is not available with `no_std`. + +As such `replace_with` will by default call [`core::intrinsics::abort()`](https://doc.rust-lang.org/core/intrinsics/fn.abort.html) instead, which in turn requires nightly Rust. + +Not everything is lost for stable `no_std` though, `replace_with` has one more trick up its sleeve: + +### panic = "abort" + +If you define [`panic = abort`](https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/aborting-on-panic.html) in the `[profile]` section of your crate's `Cargo.toml` … + +```toml +# Cargo.toml + +[profile.debug] +panic = "abort" + +[profile.release] +panic = "abort" +``` + +… and add the `"panic_abort"` feature to `replace_with` in the `dependencies` section of your crate's `Cargo.toml` … + +```toml +# Cargo.toml + +[dependencies.replace_with] +features = ["panic_abort"] +... +``` + +… the `"panic_abort"` feature enables the [`replace_with_or_abort_unchecked()`](https://docs.rs/replace_with/0.1.2/replace_with/fn.replace_with_or_abort_unchecked.html) function becomes on stable Rust as an `unsafe` function, a simple wrapper around `ptr::write(dest, f(ptr::read(dest)));`. + +**Word of caution:** It is crucial to only ever use this function having defined `panic = "abort"`, or else bad things may happen. It's *up to you* to uphold this invariant! + ## License Licensed under either of diff --git a/src/lib.rs b/src/lib.rs index 2544e1b..67d2649 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ //! B(String), //! } //! +//! # #[cfg(any(feature = "std", feature = "nightly"))] //! impl States { //! fn poll(&mut self) { //! replace_with_or_abort(self, |self_| match self_ { @@ -63,9 +64,17 @@ //! //! Huzzah! +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + all(not(feature = "std"), feature = "nightly"), + feature(core_intrinsics) +)] #![doc(html_root_url = "https://docs.rs/replace_with/0.1.2")] -use std::{mem, process, ptr}; +#[cfg(not(feature = "std"))] +extern crate core as std; + +use std::{mem, ptr}; struct CatchUnwind(mem::ManuallyDrop); impl Drop for CatchUnwind { @@ -197,6 +206,7 @@ pub fn replace_with_or_default T>(dest: &mut T, f: F /// B(String), /// } /// +/// # #[cfg(any(feature = "std", feature = "nightly"))] /// impl States { /// fn poll(&mut self) { /// replace_with_or_abort(self, |self_| match self_ { @@ -207,8 +217,74 @@ pub fn replace_with_or_default T>(dest: &mut T, f: F /// } /// ``` #[inline] +#[cfg(feature = "std")] pub fn replace_with_or_abort T>(dest: &mut T, f: F) { - replace_with(dest, || process::abort(), f); + replace_with(dest, || std::process::abort(), f); +} + +#[inline] +#[cfg(all(not(feature = "std"), feature = "nightly"))] +pub fn replace_with_or_abort T>(dest: &mut T, f: F) { + replace_with(dest, || unsafe { std::intrinsics::abort() }, f); +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Aborts on panic. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, the process will **abort** to +/// avoid returning control while `dest` is in a potentially invalid state. +/// +/// Unlike for `replace_with_or_abort()` users of `replace_with_or_abort_unchecked()` are expected +/// to have `features = ["panic_abort", …]` defined in `Cargo.toml` +/// and `panic = "abort"` defined in their profile for it to behave semantically correct: +/// +/// ```toml +/// # Cargo.toml +/// +/// [profile.debug] +/// panic = "abort" +/// +/// [profile.release] +/// panic = "abort" +/// ``` +/// +/// **Word of caution:** It is crucial to only ever use this function having defined `panic = "abort"`, +/// or else bad things may happen. It's *up to you* to uphold this invariant! +/// +/// If this behaviour is undesirable, use [replace_with] or [replace_with_or_default]. +/// +/// Equivalent to `replace_with(dest, || process::abort(), f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// enum States { +/// A(String), +/// B(String), +/// } +/// +/// impl States { +/// fn poll(&mut self) { +/// unsafe { +/// replace_with_or_abort_unchecked(self, |self_| match self_ { +/// States::A(a) => States::B(a), +/// States::B(a) => States::A(a), +/// }); +/// } +/// } +/// } +/// ``` +/// +#[inline] +#[cfg(feature = "panic_abort")] +pub unsafe fn replace_with_or_abort_unchecked T>(dest: &mut T, f: F) { + ptr::write(dest, f(ptr::read(dest))); } #[cfg(test)] @@ -236,7 +312,6 @@ mod test { // SOFTWARE. use super::*; - use std::panic; #[test] fn it_works_recover() { @@ -246,12 +321,21 @@ mod test { B, }; impl Drop for Foo { + #[cfg(feature = "std")] fn drop(&mut self) { match *self { Foo::A => println!("Foo::A dropped"), Foo::B => println!("Foo::B dropped"), } } + + #[cfg(not(feature = "std"))] + fn drop(&mut self) { + match *self { + Foo::A => (), + Foo::B => (), + } + } } let mut quax = Foo::A; replace_with( @@ -265,8 +349,11 @@ mod test { assert_eq!(&quax, &Foo::B); } + #[cfg(feature = "std")] #[test] fn it_works_recover_panic() { + use std::panic; + #[derive(PartialEq, Eq, Debug)] enum Foo { A,