Skip to content

Latest commit

 

History

History
117 lines (81 loc) · 5.36 KB

README.md

File metadata and controls

117 lines (81 loc) · 5.36 KB

replace_with

Crates.io MIT / Apache 2.0 licensed Build Status

📖 Docs | 💬 Chat

Temporarily take ownership of a value at a mutable location, and replace it with a new value based on the old one.

This crate provides the function replace_with(), which is like std::mem::replace() except it allows the replacement value to be mapped from the original value.

See RFC 1736 for a lot of discussion as to its merits. It was never merged, and the desired ability to temporarily move out of &mut T doesn't exist yet, so this crate is my interim solution.

It's very akin to take_mut, though uses Drop instead of std::panic::catch_unwind() to react to unwinding, which avoids the optimisation barrier of calling the extern "C" __rust_maybe_catch_panic(). As such it's up to ∞x faster. The API also attempts to make slightly more explicit the behavior on panic – replace_with() accepts two closures such that aborting in the "standard case" where the mapping closure (FnOnce(T) -> T) panics (as take_mut::take() does) is avoided. If the second closure (FnOnce() -> T) panics, however, then it does indeed abort. The "abort on first panic" behaviour is available with replace_with_or_abort().

Example

Consider this motivating example:

enum States {
    A(String),
    B(String),
}

impl States {
    fn poll(&mut self) {
        // error[E0507]: cannot move out of borrowed content
        *self = match *self {
            //        ^^^^^ cannot move out of borrowed content
            States::A(a) => States::B(a),
            States::B(a) => States::A(a),
        };
    }
}

Depending on context this can be quite tricky to work around. With this crate, however:

enum States {
    A(String),
    B(String),
}

impl States {
    fn poll(&mut self) {
        replace_with_or_abort(self, |self_| match self_ {
            States::A(a) => States::B(a),
            States::B(a) => States::A(a),
        });
    }
}

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:

# Cargo.toml

[dependencies.replace_with]
version = ...
default-features = false
features = []
...

The replace_with() & replace_with_or_default() functions are available on stable Rust both, with and without std.

The replace_with_or_abort() function however by default makes use of std::process::abort() which is not available with no_std.

As such replace_with will by default call core::intrinsics::abort() 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 in the [profile] section of your crate's Cargo.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

# Cargo.toml

[dependencies.replace_with]
features = ["panic_abort"]
...

… the "panic_abort" feature enables the replace_with_or_abort_unchecked() 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

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.