Module rustc_typeck::check::regionck
[−]
[src]
rustc_private
)The region check is a final pass that runs over the AST after we have inferred the type constraints but before we have actually finalized the types. Its purpose is to embed a variety of region constraints. Inserting these constraints as a separate pass is good because (1) it localizes the code that has to do with region inference and (2) often we cannot know what constraints are needed until the basic types have been inferred.
Interaction with the borrow checker
In general, the job of the borrowck module (which runs later) is to check that all soundness criteria are met, given a particular set of regions. The job of this module is to anticipate the needs of the borrow checker and infer regions that will satisfy its requirements. It is generally true that the inference doesn't need to be sound, meaning that if there is a bug and we inferred bad regions, the borrow checker should catch it. This is not entirely true though; for example, the borrow checker doesn't check subtyping, and it doesn't check that region pointers are always live when they are used. It might be worthwhile to fix this so that borrowck serves as a kind of verification step -- that would add confidence in the overall correctness of the compiler, at the cost of duplicating some type checks and effort.
Inferring the duration of borrows, automatic and otherwise
Whenever we introduce a borrowed pointer, for example as the result of
a borrow expression let x = &data
, the lifetime of the pointer x
is always specified as a region inference variable. regionck
has the
job of adding constraints such that this inference variable is as
narrow as possible while still accommodating all uses (that is, every
dereference of the resulting pointer must be within the lifetime).
Reborrows
Generally speaking, regionck
does NOT try to ensure that the data
data
will outlive the pointer x
. That is the job of borrowck. The
one exception is when "re-borrowing" the contents of another borrowed
pointer. For example, imagine you have a borrowed pointer b
with
lifetime L1 and you have an expression &*b
. The result of this
expression will be another borrowed pointer with lifetime L2 (which is
an inference variable). The borrow checker is going to enforce the
constraint that L2 < L1, because otherwise you are re-borrowing data
for a lifetime larger than the original loan. However, without the
routines in this module, the region inferencer would not know of this
dependency and thus it might infer the lifetime of L2 to be greater
than L1 (issue #3148).
There are a number of troublesome scenarios in the tests
region-dependent-*.rs
, but here is one example:
struct Foo { i: i32 } struct Bar { foo: Foo } fn get_i<'a>(x: &'a Bar) -> &'a i32 { let foo = &x.foo; // Lifetime L1 &foo.i // Lifetime L2 }
Note that this comes up either with &
expressions, ref
bindings, and autorefs
, which are the three ways to introduce
a borrow.
The key point here is that when you are borrowing a value that is "guaranteed" by a borrowed pointer, you must link the lifetime of that borrowed pointer (L1, here) to the lifetime of the borrow itself (L2). What do I mean by "guaranteed" by a borrowed pointer? I mean any data that is reached by first dereferencing a borrowed pointer and then either traversing interior offsets or boxes. We say that the guarantor of such data is the region of the borrowed pointer that was traversed. This is essentially the same as the ownership relation, except that a borrowed pointer never owns its contents.
Structs
RegionCtxt | [Unstable] |
RepeatingScope | [Unstable] |
Enums
SubjectNode | [Unstable] |