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:
- Wrap the transaction type into one mich contains a
ManualDrop<UnsafeCell<Transaction<'static>>. We call the typeTransactionWrapper. - The
create_transaction(&'s mut self)method will now internal create a transaction with the signatureTransaction<'s>wrap it into aUnsafeCelland then transmute it to'staticerasing the original lifetime (we call the wr). - To still keep the original lifetime
'saBound<'s, TransactionWrapper>is returned. - The methods on
GeneralTransactionaccept aBound<'c, Self>where, due to the constraints ofBound'cis 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. - On drop we manually drop the
Transaction<'static>in theBoundExt::pre_drop()call instead of the normal drop call, also we do so after turning it back to the right lifetime. - For usability
TransactionWrappershould contain methods to get&/&mutof the correct inner type from a&/&mutto aBound<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:
- The
Boundtype 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. - The
BoundExttrait needed to handle drop wrt. to some specialization edge cases. - 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§
- create_
gal_ wrapper_ type - Creates a wrapper type for a type with a single lifetime parameter lifting the lifetime to
Bound.
Structs§
- Bound
- Workaround for rust not having generic associated lifetimes (GAT/GAL).