[−][src]Crate galemu
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 aUnsafeCell
and then transmute it to'static
erasing the original lifetime (we call the wr). - To still keep the original lifetime
's
aBound<'s, TransactionWrapper>
is returned. - The methods on
GeneralTransaction
accept aBound<'c, Self>
where, due to the constraints ofBound
'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. - 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
TransactionWrapper
should contain methods to get&
/&mut
of the correct inner type from a&
/&mut
to 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
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. - The
BoundExt
trait 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 |
Structs
Bound | Workaround for rust not having generic associated lifetimes (GAT/GAL). |
Traits
BoundExt |