Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
5a8191f
Updated dates
Ko496-glitch Mar 14, 2026
72cdae7
add candidate preference reason
lcnr Mar 15, 2026
d4d17f5
Merge pull request #2797 from lcnr/main
lcnr Mar 15, 2026
0a821cd
Prepare for merging from rust-lang/rust
invalid-email-address Mar 16, 2026
c930610
Merge ref '1e2183119f0e' from rust-lang/rust
invalid-email-address Mar 16, 2026
bcfe977
Merge pull request #2798 from rust-lang/rustc-pull
tshepang Mar 16, 2026
7d82c5d
Fix invalid add of duplicated call locations for the rustdoc scraped …
GuillaumeGomez Mar 17, 2026
00ff819
Prepare for merging from rust-lang/rust
invalid-email-address Mar 18, 2026
f13e2b8
Merge ref '91021ccc7904' from rust-lang/rust
invalid-email-address Mar 18, 2026
ab8492d
Merge pull request #2799 from rust-lang/rustc-pull
tshepang Mar 18, 2026
c399464
Add regression test for #153837
GuillaumeGomez Mar 17, 2026
5ef93c1
Add missing num_internals feature gate to coretests/benches
bjorn3 Mar 20, 2026
d54cf8f
core: Implement `unchecked_funnel_{shl,shr}`
tgross35 Mar 20, 2026
fe363d0
enzyme submodule update
ZuseZ4 Mar 21, 2026
7cd70e3
updated the llvm 21 and 22 links
Ko496-glitch Mar 22, 2026
8059f91
Prepare for merging from rust-lang/rust
invalid-email-address Mar 23, 2026
7af2fef
Merge ref '562dee4820c4' from rust-lang/rust
invalid-email-address Mar 23, 2026
4e2ede2
Merge pull request #2802 from rust-lang/rustc-pull
reddevilmidzy Mar 23, 2026
f0e33ae
removed date-check
Ko496-glitch Mar 23, 2026
23bcfc1
Merge pull request #2796 from Ko496-glitch/update-date-check-march-2026
jyn514 Mar 23, 2026
ff75894
Remove an unhelpful assertion from `force_from_dep_node_inner`
Zalathar Mar 23, 2026
b29d6df
Remove a redundant query cache lookup while forcing
Zalathar Mar 23, 2026
4b52951
Combine `force_from_dep_node_inner` and `force_query`
Zalathar Mar 23, 2026
97b1c31
Also restore the wrapper closure for `promote_from_disk_fn`
Zalathar Mar 23, 2026
6772c82
Update books
rustbot Mar 23, 2026
953210c
Fix `doc_cfg` not working as expected on trait impls
GuillaumeGomez Mar 16, 2026
73fe4c5
Add regression test for #153655
GuillaumeGomez Mar 16, 2026
6fcd011
Correctly handle rustdoc `PlaceholderImplItem` in lints
GuillaumeGomez Mar 16, 2026
24e40f0
Add more comments to explain code
GuillaumeGomez Mar 17, 2026
19876d1
Simplify code and improve code comments
GuillaumeGomez Mar 23, 2026
ae3d87c
Update shebang reference names
ehuss Mar 23, 2026
90ea993
Rename various query cycle things.
nnethercote Mar 17, 2026
da645bd
sembr src/diagnostics/lintstore.md
tshepang Mar 23, 2026
0c3435c
use the more standard column length
tshepang Mar 23, 2026
b638ac4
sembr src/solve/candidate-preference.md
tshepang Mar 23, 2026
320d089
improve solve/candidate-preference.md
tshepang Mar 23, 2026
3b82679
avoid inline external links
tshepang Mar 23, 2026
d029d74
add date-check marker to recent section
tshepang Mar 23, 2026
3ac4ceb
sembr src/borrow-check/region-inference/member-constraints.md
tshepang Mar 23, 2026
c47c07e
whitespace
tshepang Mar 23, 2026
a1e853f
sembr src/early-late-parameters.md
tshepang Mar 23, 2026
85e8310
improve early-late-parameters.md
tshepang Mar 23, 2026
0ad2ede
sembr src/parallel-rustc.md
tshepang Mar 23, 2026
7a18ffb
improve parallel-rustc.md
tshepang Mar 23, 2026
d8d68ba
sembr src/tests/ci.md
tshepang Mar 23, 2026
61d960f
more area to click
tshepang Mar 23, 2026
86319b6
Merge pull request #2803 from rust-lang/tshepang/sembr
tshepang Mar 23, 2026
de15ab0
Store value/side-effect index lists inside `CacheEncoder`
Zalathar Mar 22, 2026
afffa7d
Don't store current-session side effects in `OnDiskCache`
Zalathar Mar 17, 2026
db19c0c
Inline `TyCtxt::load_side_effect` into its only caller
Zalathar Mar 23, 2026
ee15154
Remove an unhelpful assertion from `try_load_query_value`
Zalathar Mar 23, 2026
8a2bd0f
Rollup merge of #153964 - GuillaumeGomez:fix-trait-impl-doc-cfg, r=lo…
jhpratt Mar 24, 2026
1e2a7dc
Rollup merge of #153979 - nnethercote:rename-cycle-things, r=petroche…
jhpratt Mar 24, 2026
355b4f0
Rollup merge of #154132 - bjorn3:fix_missing_feature_gate, r=Mark-Sim…
jhpratt Mar 24, 2026
04f6332
Rollup merge of #154153 - tgross35:funnel-unchecked, r=Mark-Simulacrum
jhpratt Mar 24, 2026
efa7a5e
Rollup merge of #154236 - Zalathar:force-query, r=nnethercote
jhpratt Mar 24, 2026
39343f5
Rollup merge of #154252 - Zalathar:on-disk-cache, r=nnethercote
jhpratt Mar 24, 2026
429c8d4
Rollup merge of #154017 - GuillaumeGomez:duplicated-locs-scraped, r=U…
jhpratt Mar 24, 2026
f098563
Rollup merge of #154163 - ZuseZ4:update-enzyme-march-21-2026, r=oli-obk
jhpratt Mar 24, 2026
3574d57
Rollup merge of #154264 - rustbot:docs-update, r=ehuss
jhpratt Mar 24, 2026
04975e4
Rollup merge of #154282 - tshepang:rdg-sync, r=tshepang
jhpratt Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
sembr src/borrow-check/region-inference/member-constraints.md
  • Loading branch information
tshepang committed Mar 23, 2026
commit 3ac4ceb5b87f845ab092b4b209f7456de7eda97a
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# Member constraints

A member constraint `'m member of ['c_1..'c_N]` expresses that the
region `'m` must be *equal* to some **choice regions** `'c_i` (for
some `i`). These constraints cannot be expressed by users, but they
arise from `impl Trait` due to its lifetime capture rules. Consider a
function such as the following:
region `'m` must be *equal* to some **choice regions** `'c_i` (for some `i`).
These constraints cannot be expressed by users, but they
arise from `impl Trait` due to its lifetime capture rules.
Consider a function such as the following:

```rust,ignore
fn make(a: &'a u32, b: &'b u32) -> impl Trait<'a, 'b> { .. }
```

Here, the true return type (often called the "hidden type") is only
permitted to capture the lifetimes `'a` or `'b`. You can kind of see
permitted to capture the lifetimes `'a` or `'b`.
You can kind of see
this more clearly by desugaring that `impl Trait` return type into its
more explicit form:

Expand All @@ -23,16 +24,17 @@ fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> { .. }
Here, the idea is that the hidden type must be some type that could
have been written in place of the `impl Trait<'x, 'y>` -- but clearly
such a type can only reference the regions `'x` or `'y` (or
`'static`!), as those are the only names in scope. This limitation is
`'static`!), as those are the only names in scope.
This limitation is
then translated into a restriction to only access `'a` or `'b` because
we are returning `MakeReturn<'a, 'b>`, where `'x` and `'y` have been
replaced with `'a` and `'b` respectively.

## Detailed example

To help us explain member constraints in more detail, let's spell out
the `make` example in a bit more detail. First off, let's assume that
you have some dummy trait:
the `make` example in a bit more detail.
First off, let's assume that you have some dummy trait:

```rust,ignore
trait Trait<'a, 'b> { }
Expand All @@ -49,8 +51,8 @@ fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> {
```

What happens in this case is that the return type will be `(&'0 u32, &'1 u32)`,
where `'0` and `'1` are fresh region variables. We will have the following
region constraints:
where `'0` and `'1` are fresh region variables.
We will have the following region constraints:

```txt
'0 live at {L}
Expand All @@ -67,11 +69,11 @@ return tuple is constructed to where it is returned (in fact, `'0` and
`'1` might have slightly different liveness sets, but that's not very
interesting to the point we are illustrating here).

The `'a: '0` and `'b: '1` constraints arise from subtyping. When we
construct the `(a, b)` value, it will be assigned type `(&'0 u32, &'1
The `'a: '0` and `'b: '1` constraints arise from subtyping.
When we construct the `(a, b)` value, it will be assigned type `(&'0 u32, &'1
u32)` -- the region variables reflect that the lifetimes of these
references could be made smaller. For this value to be created from
`a` and `b`, however, we do require that:
references could be made smaller.
For this value to be created from `a` and `b`, however, we do require that:

```txt
(&'a u32, &'b u32) <: (&'0 u32, &'1 u32)
Expand All @@ -82,35 +84,39 @@ which means in turn that `&'a u32 <: &'0 u32` and hence that `'a: '0`

Note that if we ignore member constraints, the value of `'0` would be
inferred to some subset of the function body (from the liveness
constraints, which we did not write explicitly). It would never become
constraints, which we did not write explicitly).
It would never become
`'a`, because there is no need for it too -- we have a constraint that
`'a: '0`, but that just puts a "cap" on how *large* `'0` can grow to
become. Since we compute the *minimal* value that we can, we are happy
to leave `'0` as being just equal to the liveness set. This is where
member constraints come in.
`'a: '0`, but that just puts a "cap" on how *large* `'0` can grow to become.
Since we compute the *minimal* value that we can, we are happy
to leave `'0` as being just equal to the liveness set.
This is where member constraints come in.

## Choices are always lifetime parameters

At present, the "choice" regions from a member constraint are always lifetime
parameters from the current function. As of <!-- date-check --> March 2026,
this falls out from the placement of impl Trait, though in the future it may not
be the case. We take some advantage of this fact, as it simplifies the current
code. In particular, we don't have to consider a case like `'0 member of ['1,
be the case.
We take some advantage of this fact, as it simplifies the current code.
In particular, we don't have to consider a case like `'0 member of ['1,
'static]`, in which the value of both `'0` and `'1` are being inferred and hence
changing. See [rust-lang/rust#61773][#61773] for more information.
changing.
See [rust-lang/rust#61773][#61773] for more information.

[#61773]: https://github.com/rust-lang/rust/issues/61773

## Applying member constraints

Member constraints are a bit more complex than other forms of
constraints. This is because they have a "or" quality to them -- that
Member constraints are a bit more complex than other forms of constraints.
This is because they have a "or" quality to them -- that
is, they describe multiple choices that we must select from. E.g., in
our example constraint `'0 member of ['a, 'b, 'static]`, it might be
that `'0` is equal to `'a`, `'b`, *or* `'static`. How can we pick the
correct one? What we currently do is to look for a *minimal choice*
-- if we find one, then we will grow `'0` to be equal to that minimal
choice. To find that minimal choice, we take two factors into
that `'0` is equal to `'a`, `'b`, *or* `'static`.
How can we pick the correct one?
What we currently do is to look for a *minimal choice*
-- if we find one, then we will grow `'0` to be equal to that minimal choice.
To find that minimal choice, we take two factors into
consideration: lower and upper bounds.

### Lower bounds
Expand All @@ -121,30 +127,34 @@ apply member constraints, we've already *computed* the lower bounds of
`'0` because we computed its minimal value (or at least, the lower
bounds considering everything but member constraints).

Let `LB` be the current value of `'0`. We know then that `'0: LB` must
hold, whatever the final value of `'0` is. Therefore, we can rule out
Let `LB` be the current value of `'0`.
We know then that `'0: LB` must hold, whatever the final value of `'0` is.
Therefore, we can rule out
any choice `'choice` where `'choice: LB` does not hold.

Unfortunately, in our example, this is not very helpful. The lower
bound for `'0` will just be the liveness set `{L}`, and we know that
all the lifetime parameters outlive that set. So we are left with the
same set of choices here. (But in other examples, particularly those
Unfortunately, in our example, this is not very helpful.
The lower bound for `'0` will just be the liveness set `{L}`, and we know that
all the lifetime parameters outlive that set.
So we are left with the same set of choices here.
(But in other examples, particularly those
with different variance, lower bound constraints may be relevant.)

### Upper bounds

The *upper bounds* are those lifetimes that *must outlive* `'0` --
i.e., that `'0` must be *smaller* than. In our example, this would be
`'a`, because we have the constraint that `'a: '0`. In more complex
examples, the chain may be more indirect.
`'a`, because we have the constraint that `'a: '0`.
In more complex examples, the chain may be more indirect.

We can use upper bounds to rule out members in a very similar way to
lower bounds. If UB is some upper bound, then we know that `UB:
lower bounds.
If UB is some upper bound, then we know that `UB:
'0` must hold, so we can rule out any choice `'choice` where `UB:
'choice` does not hold.

In our example, we would be able to reduce our choice set from `['a,
'b, 'static]` to just `['a]`. This is because `'0` has an upper bound
'b, 'static]` to just `['a]`.
This is because `'0` has an upper bound
of `'a`, and neither `'a: 'b` nor `'a: 'static` is known to hold.

(For notes on how we collect upper bounds in the implementation, see
Expand All @@ -153,39 +163,45 @@ of `'a`, and neither `'a: 'b` nor `'a: 'static` is known to hold.
### Minimal choice

After applying lower and upper bounds, we can still sometimes have
multiple possibilities. For example, imagine a variant of our example
using types with the opposite variance. In that case, we would have
the constraint `'0: 'a` instead of `'a: '0`. Hence the current value
of `'0` would be `{L, 'a}`. Using this as a lower bound, we would be
multiple possibilities.
For example, imagine a variant of our example
using types with the opposite variance.
In that case, we would have the constraint `'0: 'a` instead of `'a: '0`.
Hence the current value of `'0` would be `{L, 'a}`.
Using this as a lower bound, we would be
able to narrow down the member choices to `['a, 'static]` because `'b:
'a` is not known to hold (but `'a: 'a` and `'static: 'a` do hold). We
would not have any upper bounds, so that would be our final set of choices.
'a` is not known to hold (but `'a: 'a` and `'static: 'a` do hold).
We would not have any upper bounds, so that would be our final set of choices.

In that case, we apply the **minimal choice** rule -- basically, if
one of our choices if smaller than the others, we can use that. In
this case, we would opt for `'a` (and not `'static`).
one of our choices if smaller than the others, we can use that.
In this case, we would opt for `'a` (and not `'static`).

This choice is consistent with the general 'flow' of region
propagation, which always aims to compute a minimal value for the
region being inferred. However, it is somewhat arbitrary.
region being inferred.
However, it is somewhat arbitrary.

<a id="collecting"></a>

### Collecting upper bounds in the implementation

In practice, computing upper bounds is a bit inconvenient, because our
data structures are setup for the opposite. What we do is to compute
data structures are setup for the opposite.
What we do is to compute
the **reverse SCC graph** (we do this lazily and cache the result) --
that is, a graph where `'a: 'b` induces an edge `SCC('b) ->
SCC('a)`. Like the normal SCC graph, this is a DAG. We can then do a
depth-first search starting from `SCC('0)` in this graph. This will
take us to all the SCCs that must outlive `'0`.
that is, a graph where `'a: 'b` induces an edge `SCC('b) -> SCC('a)`.
Like the normal SCC graph, this is a DAG.
We can then do a depth-first search starting from `SCC('0)` in this graph.
This will take us to all the SCCs that must outlive `'0`.

One wrinkle is that, as we walk the "upper bound" SCCs, their values
will not yet have been fully computed. However, we **have** already
will not yet have been fully computed.
However, we **have** already
applied their liveness constraints, so we have some information about
their value. In particular, for any regions representing lifetime
their value.
In particular, for any regions representing lifetime
parameters, their value will contain themselves (i.e., the initial
value for `'a` includes `'a` and the value for `'b` contains `'b`). So
we can collect all of the lifetime parameters that are reachable,
value for `'a` includes `'a` and the value for `'b` contains `'b`).
So we can collect all of the lifetime parameters that are reachable,
which is precisely what we are interested in.