rental 0.3.3

A macro to generate self-borrowing structs, plus premade types for convenience.

Rental - A Macro to generate self-borrowing structs

Documentation

It can sometimes occur in the course of designing an API that you find yourself in a situation where you need to store, in a single struct, both an owned value and a borrow of that value. Rust's concept of ownership and borrowing is quite flexible, but can't quite express such a scenario.

This crate uses the term "rental" to describe this concept of a borrow that co-exsists with its owner in the same struct. The borrow itself is called the "rented" type. The "owner", naturally, is the item in the struct that owns the borrow.

The API consists of the rental macro, which generates rental structs, and a few premade instantiations of this macro handling rented bare references. If you only need to rent references, see RentRef and RentMut, as well as the associated type aliases for common rental scenarios. The documentation for the rental macro describes the kinds of items that can be generated.

One example might be libloading. That crate provides a Library struct that defines methods to borrow Symbols from it. These symbols are bounded by the lifetime of the library, and are thus considered a borrow. Under normal circumstances, one would be unable to store both the library and the symbols within a single struct, but the macro defined in this crate allows you to define a struct that is capable of storing both simultaneously.

Such a struct can be declared like this:

rental!{
    mod rent_lib {
        use std::sync::Arc;
        use libloading::*;

        pub rental RentSym<'rental, S: ['rental]>(Arc<Library>, Symbol<'rental, S>): Deref(S);
    }
}

This defines a new struct that can hold an Arc of a Library, and a Symbol loaded from it. An Arc is used here so that the library can be shared with other such structs. The 'rental lifetime is handled specially and represents the shared connection between the owner and the borrow. The lifetime 'rental appears as the bound on the Symbol, because that is the lifetime that will be attached when you obtain the Symbol by calling Library::get inside the creation closure.

For a type to be eligible as an owner, it must also implement the FixedDeref trait. Because the rental struct can be moved freely, the internal borrow could be invalidated if it refers to data that has been moved. For this reason, The owner must be Deref, and furthermove must deref to a fixed memory address for the life of the rental. FixedDeref is an unsafe trait that reflects these requirements. Finally, after the declaration, you may optionally add : Deref(Target) where Target is the Deref target of the rented type. This can't be inferred, because it must be checked to ensure that it does NOT contain the 'rental lifetime anywhere in its signature. If it does, then implementing Deref would be unsafe, and the macro will reject it.

To create an instance:

let sym = RentSym::try_new(lib, |lib| unsafe { lib.get::<fn()>(b"my_symbol") }).unwrap();

NOTE: The unsafe here is solely because loading a symbol from the dylib is unsafe, unrelated to this library, I promise :)

Inside this closure, the 'rental lifetime becomes "existential" and cannot be satisfied by anything outside the closure, preventing anything tied to it from being exfiltrated. The function held by the struct can then be called by simply:

sym();

In this way, the symbol can be carried around and called as a single unit, but retains within it the library that it depends on. The user is thus relieved of the concern of keeping the library alive, or even knowing that it exists at all.

In cases where the rented valued is not Deref or does not meet the requirements for a safe Deref impl as described earlier, you can access the internal value with the rent method, which will pass to you an existentially bound reference to the rented value. The example above could also be written like this:

sym.rent(|s| s());