RentToOwn<T>
: A wrapper type for optionally giving up ownership of the
underlying value.
RentToOwn<T>
is useful in situations where
a function might want to conditionally take ownership of some T
value, and
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.
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.
```rust use renttoown::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 {
// ...
}
struct Config { // ... }
/// A big, expensive resource that has been properly configured. struct ConfiguredResource { resource: BigExpensiveResource, config: Config, }
fn readandparseconfigfile() -> io::Result
fn configure<'a>(
resource: &'a mut RentToOwn<'a, BigExpensiveResource>
) -> io::Result?
. 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 = readandparseconfigfile()?;
// 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.
``rust
fn use_custom_configuration_or_default(resource: BigExpensiveResource) -> ConfiguredResource {
// We pass the resource into
withand it constructs the
RentToOwn
// wrapper around it and then gives the wrapper to the closure. Finally, it
// returns a pair of an
Optionwhich is
Someif
// 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