Module lock_order::relation
source · Expand description
Traits used to describe lock-ordering relationships.
This crate defines the traits used to describe lock-ordering relationships,
LockBefore
and LockAfter
. They are reciprocals, like From
and
Into
in the standard library, and like From
and Into
, a blanket impl
is provided for LockBefore
for implementations of LockAfter
.
It’s recommended that, instead of implementing LockAfter<B> for A
on your
own lock level types A
and B
, you use the [impl_lock_after
] macro
instead. Why? Because in addition to emitting an implementation of
LockAfter
, it also provides a blanket implementation equivalent to this:
impl <X> LockAfter<X> for B where A: LockAfter<X> {}
The blanket implementations are useful for inferring trait implementations but have a more important purpose: they make it impossible to introduce cycles in our lock ordering graph, and that’s the key property we want to uphold.
To see how this happens, let’s look at the trait implementations. Suppose we have a lock ordering graph that looks like this:
A -> B -> C
Assuming we are using impl_lock_after
, that gives us the following trait
implementations:
// Graph edges
impl LockAfter<A> for B {}
impl LockAfter<B> for C {}
// Blanket impls that get us transitivity
impl<X> LockAfter<X> for B where A: LockAfter<X> {} // 1
impl<X> LockAfter<X> for C where B: LockAfter<X> {} // 2
Now suppose we added an edge C -> A
(introducing a cycle). That would give
us these two impls:
// New edge
impl LockAfter<C> for A {}
// New blanket impl
impl<X> LockAfter<X> for A where C: LockAfter<X> {}
The compiler will follow the blanket impls to produce implicit LockAfter
implementations like this:
- Our added edge satisfies the
where
clause for blanket impl 1 withX=C
, so the compiler infers an implicitimpl LockAfter<C> for B {}
. - That satisfies the conditions for blanket impl 2 (
X=C
), so now we also haveimpl LockAfter<C> for C {}
. - This satisfies the condition for our new blanket impl with
X=C
; now the compiler adds an implicitimpl LockAfter<C> for A {}
.
Depicted visually, the compiler combines specific and blanket impls like this:
┌─────────────────────────┐┌──────────────────────────────────────────────────┐
│ impl LockAfter<C> for A ││ impl<X> LockAfter<X> for B where A: LockAfter<X> │
└┬────────────────────────┘└┬─────────────────────────────────────────────────┘
┌▽──────────────────────────▽┐┌──────────────────────────────────────────────────┐
│ impl LockAfter<C> for B ││ impl<X> LockAfter<X> for C where B: LockAfter<X> │
└┬───────────────────────────┘└┬─────────────────────────────────────────────────┘
┌▽─────────────────────────────▽┐┌──────────────────────────────────────────────────┐ │
│ impl LockAfter<C> for C ││ impl<X> LockAfter<X> for A where C: LockAfter<X> │ │
└┬──────────────────────────────┘└┬─────────────────────────────────────────────────┘
┌▽────────────────────────────────▽┐
│ impl LockAfter<C> for A (again) │
└──────────────────────────────────┘
The final implicit trait implementation has the exact same trait
(LockAfter<C>
) and type (A
) as the explicit implementation we added with
our graph edge, so the compiler detects the duplication and rejects our
code. This works not just with the graph above, but with any graph that
includes a cycle.
Traits§
- Marker trait that indicates that
Self
can be locked afterA
. - Marker trait that indicates that
Self
is an ancestor ofX
.