Expand description
The ObligationForest is a utility data structure used in trait
matching to track the set of outstanding obligations (those not yet
resolved to success or error). It also tracks the “backtrace” of each
pending obligation (why we are trying to figure this out in the first
place).
§External view
ObligationForest supports two main public operations (there are a
few others not discussed here):
- Add a new root obligations (
register_obligation). - Process the pending obligations (
process_obligations).
When a new obligation N is added, it becomes the root of an
obligation tree. This tree can also carry some per-tree state T,
which is given at the same time. This tree is a singleton to start, so
N is both the root and the only leaf. Each time the
process_obligations method is called, it will invoke its callback
with every pending obligation (so that will include N, the first
time). The callback also receives a (mutable) reference to the
per-tree state T. The callback should process the obligation O
that it is given and return a ProcessResult:
Unchanged-> ambiguous result. Obligation was neither a success nor a failure. It is assumed that further attempts to process the obligation will yield the same result unless something in the surrounding environment changes.Changed(C)- the obligation was shallowly successful. The vectorCis a list of subobligations. The meaning of this is thatOwas successful on the assumption that all the obligations inCare also successful. Therefore,Ois only considered a “true” success ifCis empty. Otherwise,Ois put into a suspended state and the obligations inCbecome the new pending obligations. They will be processed the next time you callprocess_obligations.Error(E)-> obligation failed with errorE. We will collect this error and return it fromprocess_obligations, along with the “backtrace” of obligations (that is, the list of obligations up to and including the root of the failed obligation). No further obligations from that same tree will be processed, since the tree is now considered to be in error.
When the call to process_obligations completes, you get back an Outcome,
which includes two bits of information:
completed: a list of obligations where processing was fully completed without error (meaning that all transitive subobligations have also been completed). So, for example, if the callback fromprocess_obligationsreturnsChanged(C)for some obligationO, thenOwill be considered completed right away ifCis the empty vector. Otherwise it will only be considered completed once all the obligations inChave been found completed.errors: a list of errors that occurred and associated backtraces at the time of error, which can be used to give context to the user.
Upon completion, none of the existing obligations were shallowly
successful (that is, no callback returned Changed(_)). This implies that
all obligations were either errors or returned an ambiguous result.
§Implementation details
For the most part, comments specific to the implementation are in the
code. This file only contains a very high-level overview. Basically,
the forest is stored in a vector. Each element of the vector is a node
in some tree. Each node in the vector has the index of its dependents,
including the first dependent which is known as the parent. It also
has a current state, described by NodeState. After each processing
step, we compress the vector to remove completed and error nodes, which
aren’t needed anymore.
Modules§
Structs§
Enums§
- Node
State 🔒 - The state of one node in some tree within the forest. This represents the
current state of processing for the obligation (of type
O) associated with this node. - Process
Result - The result type used by
process_obligation.
Traits§
- Forest
Obligation - Obligation
Processor - Outcome
Trait - This trait allows us to have two different Outcome types: