#take #move #cell #borrow #rent

rent_to_own

A wrapper type for optionally giving up ownership of the underlying value

1 unstable release

Uses old Rust 2015

0.1.0 Nov 29, 2017

#2581 in Rust patterns

Download history 344/week @ 2023-10-30 291/week @ 2023-11-06 382/week @ 2023-11-13 260/week @ 2023-11-20 237/week @ 2023-11-27 217/week @ 2023-12-04 256/week @ 2023-12-11 259/week @ 2023-12-18 159/week @ 2023-12-25 90/week @ 2024-01-01 253/week @ 2024-01-08 267/week @ 2024-01-15 282/week @ 2024-01-22 110/week @ 2024-01-29 139/week @ 2024-02-05 158/week @ 2024-02-12

700 downloads per month
Used in 13 crates (via state_machine_future)

Apache-2.0/MIT

16KB
74 lines

rent_to_own

Build Status

RentToOwn<T>: A wrapper type for optionally giving up ownership of the underlying value.

RentToOwn<T> is useful in situations where

  1. a function might want to conditionally take ownership of some T value, and

  2. that function cannot take the T by value and return an Option<T> to maybe give the T value back if it doesn't want ownership.

RentToOwn<T> dereferences (immutably and mutably) to its inner T value, and additionally provides a take method that gives up ownership of the inner value to the caller.

Under the covers, RentToOwn<T> is essentially an Option<T> that gets unwrapped when dereferenced and calls Option::take if we need to take ownership of the inner value. The key advantage over using Option<T> directly, other than the Deref sugar, is some lifetime trickery to statically prevent all unwrapping panics that would arise from using the RentToOwn<T> wrapper again after the inner value has been taken. Once the inner value is taken, the borrow checker will ensure that the original RentToOwn<T> cannot be used anymore. See the take method's documentation for details.

Example

In this example, if the configure function encounters any errors, we do not wish to drop the BigExpensiveResource, but instead allow the caller to handle the error and then reuse the resource. In effect, the configure function is conditionally taking ownership of the BigExpensiveResource depending on if there are IO errors or not.

use rent_to_own::RentToOwn;

use std::io::{self, Read};
use std::fs;

/// This is a big, expensive to create (or maybe even unique) resource, and we
/// want to reuse it even if `configure` returns an error.
struct BigExpensiveResource {
    // ...
}

#[derive(Default)]
struct Config {
    // ...
}

/// A big, expensive resource that has been properly configured.
struct ConfiguredResource {
    resource: BigExpensiveResource,
    config: Config,
}

fn read_and_parse_config_file() -> io::Result<Config> {
    // ...
}

fn configure<'a>(
    resource: &'a mut RentToOwn<'a, BigExpensiveResource>
) -> io::Result<ConfiguredResource> {
    // We use normal error propagation with `?`. Because we haven't `take`n the
    // resource out of the `RentToOwn`, if we early return here the caller still
    // controls the `BigExpensiveResource` and it isn't dropped.
    let config = read_and_parse_config_file()?;

    // Now we `take` ownership of the resource and return the configured
    // resource.
    let resource = resource.take();
    Ok(ConfiguredResource { resource, config })
}

What does configure's caller look like? It calls RentToOwn::with to construct the RentToOwn<BigExpensiveResource> and invoke a closure with it. Then it inspects the results of the closure and whether the BigExpensiveResource was taken or not.

In this example, the caller can recover from any IO error when reading or parsing the configuration file and use a default configuration with the BigExpensiveResource instead.

fn use_custom_configuration_or_default(resource: BigExpensiveResource) -> ConfiguredResource {
    // We pass the resource into `with` and it constructs the `RentToOwn`
    // wrapper around it and then gives the wrapper to the closure. Finally, it
    // returns a pair of an `Option<BigExpensiveResource>` which is `Some` if
    // the closure took ownership and `None` if it did not, and the closure's
    // return value.
    let (resource, result) = RentToOwn::with(resource, |resource| {
        configure(resource)
    });

    if let Ok(configured) = result {
        return configured;
    }

    // Reuse the resource if the closure did not take ownership or else
    // reconstruct it if the closure did take ownership. (In this particular
    // example, we know that `configure` took ownership if and only if the
    // result was `Ok`, but that doesn't hold for all possible examples.)
    // Finally, return the configured resource with the default configuration.
    let resource = resource.unwrap_or_else(|| BigExpensiveResource::reconstruct());
    let config = Config::default();
    ConfiguredResource { resource, config }
}

License: Apache-2.0/MIT

No runtime deps