Coder Social home page Coder Social logo

alecmocatta / replace_with Goto Github PK

View Code? Open in Web Editor NEW
98.0 3.0 9.0 56 KB

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

License: Apache License 2.0

Rust 100.00%
rust rust-patterns mutability

replace_with's Introduction

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.

replace_with's People

Contributors

alecmocatta avatar mergify[bot] avatar regexident avatar steffahn avatar tage64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

replace_with's Issues

Improving ergonomics with Result?

The replace_with_and_return F signature of FnOnce(T) -> (U, T) is rather awkward to use with Result'ing code.

What are your thoughts on a function that behaves similarly to replace_with_and_return, but tweaked to support Result and/or Option? A non-optimal path, but not quite a panic either. Something like:

pub fn replace_with_ok<T, U, D: FnOnce() -> T, F: FnOnce(T) -> Result<T, E>(
    dest: &mut T, 
    default: D,
    f: F
) -> Result<(), E>

I imagine the panic/abort behavior would remain the same. Using the default where applicable.

Though, I could see someone requesting a panic_default: D and err_default: D, but those don't match my use case as much.

Thoughts?

Return a custom value

Hello,

Would it be possible to make some more functions which makes it possible to return a value.
In the abort case it may look like this:

pub fn replace_with_or_abort<T, U, F>(dest: &mut T, f: F) -> U where F: FnOnce(T) -> (T, U)

I can make a pr if you want.

Tage

no_std documentation does not appear on docs.rs

This isn't a huge deal, but I wanted to point out that the documentation for no_std only appears on crates.io and on GitHub. It does not appear on docs.rs. However, I would expect docs.rs to be the primary entry point for newcomers discovering this crate. I think it might be helpful if to add it there as well. There are some really important things to consider in a no_std context.

Thanks for providing this crate, by the way. It's been exactly what I needed in a handful of cases.

`replace_with[_and_return]` has undefined behavior if `default` panics.

The two functions:

Both have undefined behavior, since they can cause a double use if the default closure provided panics.

While the documentation says that default should not panic, because they are both safe functions default panicing must not cause undefined behavior.

An important note

On panic (or to be more precise, unwinding) of the closure f, default will be called to provide a replacement value. default should not panic ā€“ doing so will constitute a double panic and will most likely abort the process.

Panicing while unwinding may cause the process to abort, but you cannot rely on it for soundness, so the following example has ub.

let mut string = String::new();

replace_with(
    &mut string, 
    || panic!(),  // if this panic doesn't cause abort `string` will be freed twice
    |s| { 
        drop(s); // `sĀ“ dropped here
        
        panic!()
    }
);

drop(string); // use after free

The solution is to check if default panics and abort the process.

Question: Optimisation effects of `catch_unwind`?

In the readme to this package you state:

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.

I was wondering if you could go into detail here, in particular whether catch_unwind() causes any optimisation barriers in the case where there is no panic (it seems like it does due to the "maybe").

I've also written the following function, which does an inplace replacement of a vector. It doesn't require Default or Clone. The behaviour on failure is to take the last element of the vector and move it in the vector in the spot where the function panicked, and then simply slice the last element from the vector (as it is now moved and doesn't need to be dropped). My implementation seems to work but uses catch_unwind. I was wondering whether there was a better approach here that avoids the catch_unwind() call, particularly if it inhibits optimisation as you suggest (feel free to include any of this in your library as well).

    fn inplace_vec_map<T>(f: impl Fn(T) -> T, mut x: Vec<T>) -> Vec<T> {
        let mut ptr_and_error: Option<(*mut T, _)> = None;

        for item_ref in &mut x {
            unsafe {
                let y1: T = std::ptr::read(item_ref);
                let y2 = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(y1)));
                match y2 {
                    Ok(result) => std::ptr::write(item_ref, result),
                    Err(error) => {
                        ptr_and_error = Some((item_ref, error));
                        break;
                    }
                }
            }
        }

        if let Some((item_ptr, error)) = ptr_and_error {
            let last_index = x.len() - 1;
            unsafe {
                std::ptr::write(item_ptr, std::ptr::read(x.get_unchecked(last_index)));
                x.set_len(last_index);
                std::panic::resume_unwind(error);
            }
        }
        x
    }

Add GPLv2 compatible license (e.g. MIT or ISC)

Please add a GPLv2-compatible licensing option like MIT or ISC. This would make it practical for me to use and contribute this project, and I suppose some others may feel similarly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.