Skip to content

Added support for use with no_std #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
93 changes: 90 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_ {
Expand All @@ -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<F: FnOnce()>(mem::ManuallyDrop<F>);
impl<F: FnOnce()> Drop for CatchUnwind<F> {
Expand Down Expand Up @@ -197,6 +206,7 @@ pub fn replace_with_or_default<T: Default, F: FnOnce(T) -> 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_ {
Expand All @@ -207,8 +217,74 @@ pub fn replace_with_or_default<T: Default, F: FnOnce(T) -> T>(dest: &mut T, f: F
/// }
/// ```
#[inline]
#[cfg(feature = "std")]
pub fn replace_with_or_abort<T, F: FnOnce(T) -> 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, F: FnOnce(T) -> 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, F: FnOnce(T) -> T>(dest: &mut T, f: F) {
ptr::write(dest, f(ptr::read(dest)));
}

#[cfg(test)]
Expand Down Expand Up @@ -236,7 +312,6 @@ mod test {
// SOFTWARE.

use super::*;
use std::panic;

#[test]
fn it_works_recover() {
Expand All @@ -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(
Expand All @@ -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,
Expand Down