Crate galemu

source ·
Expand description

Create for helping you to workaround not having generic associated lifetimes.

Problem Case

Lets say you need to abstract over a connection/transaction of some form. A intuitive way would be to have something like following code (which for simplicity omits all the results and for this example irrelevant methods):

trait GeneralConnection {
    type Transaction: GeneralTransaction;
    fn create_transaction(&mut self) -> Self::Transaction;
}

trait GeneralTransaction {
    // Potential results ommited.
    fn commit(self);
    fn abort(self);
}

The problem with this is that most transactions have a signature of the form Transaction<'conn> where 'conn binds to the self parameter of the method used to construct the transaction. This currently can not be represented in rust.

In the future you might be able to use generic associated types (GAT) or the subset limited to lifetimes of it (GAL). This would allow following code:

trait GeneralConnection {
    type Transaction<'conn>: GeneralTransaction;
    // This don't work on current rust (1.30) not even in nightly.
    // Lifetimes could have been omitted.
    fn create_transaction<'conn>(&'conn mut self) -> Self::Transaction<'conn>;
}

trait GeneralTransaction {
    // Potential results ommited.
    fn commit(self);
    fn abort(self);
}

Problem Circumvention

This crate provides a patterns (and a small helper type, trait and macro) to circumvent this limitation. Note that while possible it’s not necessary a nice solution.

The idea of this create is to lift the lifetime from the type parameter into a know wrapper type, this produces following code:

use galemu::{Bound, BoundExt};

trait GeneralConnection {
    type Transaction: GeneralTransaction;
    // Lifetimes could have been omitted
    fn create_transaction<'conn>(&'conn mut self) -> Bound<'conn, Self::Transaction>;
}

trait GeneralTransaction: for<'a> BoundExt<'a> {
    // Potential results omitted.
    // Once the rust "arbitrary self types" features lands on stable this can
    // be made much nicer (by using `self: Bound<Self>`).
    fn commit<'c>(me: Bound<'c, Self>);
    fn abort<'c>(me: Bound<'c, Self>);
}

Note that Bound has some very specific safety guarantees about how it binds the 'conn lifetime to Self::Transaction and that the GeneralTransaction now accepts a lifetime bound Self. Also not that without the unstable “arbitrary self type” feature the methods will no longer have a self parameter so they will need to be called with GeneralTransaction::commit(trans) instead of trans.commit().

The trick is that now if you need to implement GeneralConnection for a with transactions of the form Transaction<'conn> you can approach it in following way:

  1. Wrap the transaction type into one mich contains a ManualDrop<UnsafeCell<Transaction<'static>>. We call the type TransactionWrapper.
  2. The create_transaction(&'s mut self) method will now internal create a transaction with the signature Transaction<'s> wrap it into a UnsafeCell and then transmute it to 'static erasing the original lifetime (we call the wr).
  3. To still keep the original lifetime 's a Bound<'s, TransactionWrapper> is returned.
  4. The methods on GeneralTransaction accept a Bound<'c, Self> where, due to the constraints of Bound 'c is guaranteed to be a “subset” of 's (as where constraint this is 's: 'c). So in the method we can turn the transaction back into the appropriate lifetime.
  5. On drop we manually drop the Transaction<'static> in the BoundExt::pre_drop() call instead of the normal drop call, also we do so after turning it back to the right lifetime.
  6. For usability TransactionWrapper should contain methods to get &/&mut of the correct inner type from a &/&mut to a Bound<TransactionWrapper>.

Note that some of the aspects (like the part about ManualDrop and pre_drop) might seem arbitrary but are needed to handle potential specialization of code based on the 'static lifetime.

What this lib provides:

  1. The Bound type for binding the lifetime to the part where it was erased in a safe way with some safety guarantees which go above a normal wrapper.
  2. The BoundExt trait needed to handle drop wrt. to some specialization edge cases.
  3. The [create_gal_wrapper_type_for] which implements all unsafe code for you.

Example

use galemu::{Bound, BoundExt, create_gal_wrapper_type};

struct Connection {
    count: usize
}

struct Transaction<'conn> {
    conn: &'conn mut Connection
}

impl Connection {
    fn transaction(&mut self) -> Transaction {
        Transaction { conn: self }
    }
}

trait GCon {
    type Transaction: GTran;

    fn create_transaction(&mut self) -> Bound<Self::Transaction>;
}

trait GTran: for<'s> BoundExt<'s> {
    fn commit<'s>(me: Bound<'s, Self>);
    fn abort<'s>(me: Bound<'s, Self>);
}

create_gal_wrapper_type!{ struct TransWrap(Transaction<'a>); }

impl GCon for Connection {
    type Transaction = TransWrap;

    fn create_transaction(&mut self) -> Bound<Self::Transaction> {
        let transaction = self.transaction();
        TransWrap::new(transaction)
    }
}

impl GTran for TransWrap {
    fn commit<'s>(me: Bound<'s, Self>) {
        let trans = TransWrap::into_inner(me);
        trans.conn.count += 10;
    }

    fn abort<'s>(me: Bound<'s, Self>) {
        let trans = TransWrap::into_inner(me);
        trans.conn.count += 3;
    }
}

fn create_commit_generic(x: &mut impl GCon) {
    let trans = x.create_transaction();
    // Using arbitrary self types this can become `trans.commit()`.
    // (extension traits for `Bound<T> where T: GTran` are also an option here).
    GTran::commit(trans)
}

fn create_abort_specific(x: &mut Connection) {
    let trans = x.create_transaction();
    GTran::abort(trans)
}

#[test]
fn it_can_be_used() {
    let mut conn = Connection { count: 0 };
    {
        create_commit_generic(&mut conn);
    }
    {
        create_abort_specific(&mut conn);
    }
    assert_eq!(conn.count, 13)
}

Macros

Creates a wrapper type for a type with a single lifetime parameter lifting the lifetime to Bound.

Structs

Workaround for rust not having generic associated lifetimes (GAT/GAL).

Traits