# Design note: old vs new design for field-wise `CoerceShared`
## Status
This is a PR-specific design note for [Support field-wise CoerceShared reborrows](https://github.com/rust-lang/rust/pull/157101)
The intended readers are reviewers already familiar with the reborrow/pre-RFC work. This document focuses on the delta from the previous `CoerceShared` design to the field-wise design in this PR.
If you are not yet familiar with reborrow take a look at the [Pre-RFC](https://internals.rust-lang.org/t/pre-rfc-reborrow-trait/22972) by
aapoalas it clearly outlines the long term goals and usecases
## Executive summary
Old design:
- `CoerceShared` was effectively limited to simple wrapper shapes.
- Validation allowed zero or one non-`PhantomData` data field on each side.
- Lowering could be treated as same-layout or copy-like for the accepted shared cases.
- Multi-field, reordered-field, source-only-field, tuple `PhantomData`, and nested-field relations were not first-class.
New design:
- `CoerceShared` is modeled as reconstructing the target ADT from validated corresponding source fields.
- Field correspondence is canonicalized in `rustc_middle::ty::reborrow`.
- Validation, borrowck, CTFE/Miri, and codegen all consume the same field-pair model.
- `&mut T -> &T` leaves are semantic shared reborrows, not typed copies.
Reason:
- The extension is needed for wrappers whose shared form is not layout-identical to the mutable form.
- It supports realistic wrappers with metadata, marker fields, and source-only state.
- It avoids phase divergence by moving field correspondence into one helper.
- It fixes the semantic hole where CTFE/Miri would otherwise model a shared reborrow as a typed copy.
## One-page old-vs-new table
| Design point | Old design / assumption | Problem | New design in this PR | Main files |
| --- | --- | --- | --- | --- |
| [Shape of supported source/target ADTs](#example-s1-shape-of-supported-source-target-adts) | Both sides already had to be struct ADTs, but validation only accepted marker-only wrappers or wrappers with exactly one non-`PhantomData` data field on each side. | This avoided defining a general source-to-target reconstruction rule. | Distinct struct ADTs can be related field-wise if canonical correspondence succeeds. | `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs` |
| [Field count assumption](#example-s2-field-count-assumption) | Zero or one non-`PhantomData` data field on each side. | Real wrappers often carry multiple data fields or metadata fields. | Any number of target data fields is allowed if every target data field has a source field. | `compiler/rustc_middle/src/ty/reborrow.rs`, `tests/ui/reborrow/coerce-shared-multi-field.rs` |
| [Named-field matching](#example-s3-named-field-matching) | With at most one data field on each side, validation did not define how multiple named fields correspond. Borrowck separately looked up destination fields by source field name. | The one-field limit meant multi-field named wrappers, including wrappers whose shared form orders fields differently, could not be represented by the impl validation rule. | Named non-`PhantomData` fields match by field name. | `compiler/rustc_middle/src/ty/reborrow.rs`, `tests/ui/reborrow/coerce-shared-reordered-field.rs` |
| [Tuple-field matching](#example-s4-tuple-field-matching) | The old accepted shape had at most one data field, so tuple position was trivial. | Tuple marker fields can shift real field indices. | Tuple non-`PhantomData` fields match by filtered ordinal, while preserving original `FieldIdx`. | `compiler/rustc_middle/src/ty/reborrow.rs`, `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs` |
| [`PhantomData`](#example-s5-phantomdata) | Ignored for old field counting, but no reusable multi-field projection model existed. | Filtering without preserving original indices would project the wrong field. | `PhantomData` is ignored for matching only; original `FieldIdx` is kept for projections. | `compiler/rustc_middle/src/ty/reborrow.rs`, `tests/ui/reborrow/coerce-shared-extra-marker.rs` |
| [Source-only fields](#example-s6-source-only-fields) | Old validation required equal data-field counts in the one-field model. | This rejects wrappers whose mutable form stores state not present in the shared view. | Source-only fields are allowed; only target fields must be produced. | `compiler/rustc_middle/src/ty/reborrow.rs`, `tests/ui/reborrow/coerce-shared-different-layout.rs` |
| [Nested wrappers / recursive `CoerceShared`](#example-s7-nested-wrappers-recursive-coerceshared) | Only one field could be checked, and lower phases did not share a recursive model. | Nested wrappers need recursive field relation and lowering. | Field validation and lower phases recurse through `CoerceShared` field pairs. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `tests/ui/reborrow/coerce-shared-nested.rs` |
| [`&mut T -> &T` leaves](#example-s8-mut-to-shared-ref-leaves) | Validation recognized this leaf, but CTFE modeled shared reborrow as copy/transmute. | CTFE/Miri can hit typed-copy failure and miss fresh-reference retagging. | Leaf conversion creates a fresh shared reborrow of the referent in CTFE/Miri. | `compiler/rustc_const_eval/src/interpret/step.rs`, `tests/ui/reborrow/coerce_shared_consteval.rs`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs` |
| [Copy-compatible leaves](#example-s9-copy-compatible-leaves) | Same field type required `Copy`. | Still needed, but must now be applied to each leaf. | Structurally equal leaves are accepted when the source field implements `Copy`. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `tests/ui/reborrow/coerce-shared-multi-field.rs` |
| [Validation phase](#example-s10-validation-phase) | `CoerceShared` validation was simple/single-field-oriented. | Invalid multi-field impls could fall through into lower-phase assumptions. | Validation walks all canonical field pairs and emits user diagnostics. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_hir_analysis/src/errors.rs` |
| [Borrowck constraints](#example-s11-borrowck-constraints) | Borrowck related destination fields by name and skipped missing source fields. | That did not match the new target-driven reconstruction rule. | Borrowck recurses through canonical field pairs and relates aliases structurally for this path. | `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_borrowck/src/type_check/relate_tys.rs` |
| [CTFE/Miri semantics](#example-s12-ctfe-miri-semantics) | Shared `Rvalue::Reborrow` used `copy_op_allow_transmute`. | Not valid for different layouts or `&mut T -> &T` leaves. | CTFE/Miri reconstructs target fields recursively and creates fresh shared-reference leaves. | `compiler/rustc_const_eval/src/interpret/step.rs` |
| [Runtime codegen semantics](#example-s13-runtime-codegen-semantics) | SSA and Cranelift read the source value and wrote it as the target. | Different layouts, omitted fields, and reordered fields are not source-as-target copies. | Codegen reconstructs distinct target ADTs recursively using the canonical field pairs. | `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, `compiler/rustc_codegen_cranelift/src/base.rs` |
| [Lower-phase `bug!` / malformed-MIR assumptions](#example-s14-lower-phase-bug-malformed-mir-assumptions) | Several paths used `bug!`, `unreachable!`, or `unwrap()` for assumed valid MIR. | With a broader design, user invalid cases need to be rejected before lower phases. | User-facing invalid impls are rejected in validation/typeck; lower phases still assert malformed MIR for phase disagreement. | `compiler/rustc_borrowck/src/borrow_set.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, codegen files |
| [Tests](#example-s15-tests) | Previous tests covered the simple model and regressions. | New semantics need per-design-point coverage. | Added UI coverage for multi-field, missing fields, source-only fields, tuple markers, generics, aliases, lifetime errors, private/foreign fields, and regressions. | `tests/ui/reborrow/*`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs` |
## Examples for the old-vs-new table
<a id="example-s1-shape-of-supported-source-target-adts"></a>
### Example S1: Shape of supported source/target ADTs
**Old design:** Both sides already had to be struct ADTs, but validation only accepted marker-only wrappers or wrappers with exactly one non-`PhantomData` data field on each side.
**New design:** Distinct struct ADTs remain required, but they can be related field-wise when the canonical correspondence succeeds.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-field-relations.rs`, `tests/ui/reborrow/coerce_shared_consteval.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T> {
value: &'a mut T,
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
}
// Old design: accepted because each side has exactly one non-PhantomData field.
// New design: also accepted; the field pair is Ref::value <- Mut::value.
// Lowering: construct Ref from Mut::value using the &mut T -> &T leaf rule.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s2-field-count-assumption"></a>
### Example S2: Field count assumption
**Old design:** Validation rejected wrappers with more than one non-`PhantomData` data field on either side.
**New design:** Multiple target fields are accepted if every target field has a corresponding source field.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-multi-field.rs`, `tests/ui/reborrow/coerce-shared-generics.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, X, Y> {
x: &'a mut X,
y: &'a mut Y,
}
impl<'a, X, Y> Reborrow for Mut<'a, X, Y> {}
#[derive(Clone, Copy)]
struct Ref<'a, X, Y> {
x: &'a X,
y: &'a Y,
}
// Old design: rejected because there are two data fields on each side.
// New design: accepted because target.x maps to source.x and target.y maps to source.y.
// Lowering: reconstruct Ref { x: shared_reborrow(*source.x), y: shared_reborrow(*source.y) }.
impl<'a, X, Y> CoerceShared<Ref<'a, X, Y>> for Mut<'a, X, Y> {}
```
<a id="example-s3-named-field-matching"></a>
### Example S3: Named-field matching
**Old design:** The one-field limit meant validation did not need to define multi-field named correspondence.
**New design:** Named non-`PhantomData` fields match by field name, not declaration order.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-reordered-field.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, X, Y> {
x: &'a mut X,
y: &'a mut Y,
}
impl<'a, X, Y> Reborrow for Mut<'a, X, Y> {}
#[derive(Clone, Copy)]
struct Ref<'a, X, Y> {
y: &'a Y,
x: &'a X,
}
// Old design: rejected before this matching question mattered, because there are two data fields.
// New design: accepted; matching is by name:
// - target.y corresponds to source.y
// - target.x corresponds to source.x
// Lowering must not assume declaration order.
impl<'a, X, Y> CoerceShared<Ref<'a, X, Y>> for Mut<'a, X, Y> {}
```
<a id="example-s4-tuple-field-matching"></a>
### Example S4: Tuple-field matching
**Old design:** With at most one data field, tuple position was trivial.
**New design:** Tuple fields match by filtered non-`PhantomData` ordinal, while projections still use original `FieldIdx`.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, PhantomData, Reborrow};
struct Mut<'a, X, Y>(
PhantomData<&'a mut X>, // ignored for matching
&'a mut X, // original source FieldIdx = 1
&'a mut Y, // original source FieldIdx = 2
);
impl<'a, X, Y> Reborrow for Mut<'a, X, Y> {}
#[derive(Clone, Copy)]
struct Ref<'a, X, Y>(
&'a X, // target ordinal 0 maps to source FieldIdx 1
PhantomData<&'a X>, // ignored for matching
&'a Y, // target ordinal 1 maps to source FieldIdx 2
);
// Old design: rejected because there are two runtime data fields.
// New design: accepted by filtered ordinal matching.
// Lowering must project source.1 and source.2, not filtered indexes 0 and 1.
impl<'a, X, Y> CoerceShared<Ref<'a, X, Y>> for Mut<'a, X, Y> {}
```
<a id="example-s5-phantomdata"></a>
### Example S5: PhantomData
**Old design:** `PhantomData` was ignored for field counting, but there was no reusable multi-field projection model.
**New design:** `PhantomData` is ignored only for correspondence; real projections still use the original fields.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-extra-marker.rs`, `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, PhantomData, Reborrow};
struct Mut<'a, T> {
marker: PhantomData<&'a mut T>,
value: &'a mut T,
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
marker: PhantomData<&'a T>,
}
// Old design: accepted this single-data-field shape, but did not establish a reusable rule.
// New design: the marker fields are ignored for matching and are not runtime data leaves.
// Field correspondence is only target.value <- source.value.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s6-source-only-fields"></a>
### Example S6: Source-only fields
**Old design:** Equal data-field count in the simple model rejected source-only state.
**New design:** Target-driven reconstruction only requires target fields to be produced; source-only fields remain protected by the whole-place loan.
**Implemented in:** `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_borrowck/src/borrow_set.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-different-layout.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T> {
value: &'a mut T,
cache: usize, // source-only field: not present in Ref
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
}
// Old design: rejected because source has two data fields and target has one.
// New design: accepted because every target field has a source field.
// Borrowck: the shared loan is for the whole Mut place, so source.cache is still protected.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s7-nested-wrappers-recursive-coerceshared"></a>
### Example S7: Nested wrappers / recursive CoerceShared
**Old design:** Only one top-level field could be checked, and lower phases did not share a recursive field model.
**New design:** A corresponding field can itself be related by `CoerceShared`, so reconstruction recurses.
**Implemented in:** `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, codegen files.
**Covered by:** `tests/ui/reborrow/coerce-shared-nested.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct InnerMut<'a, T> {
value: &'a mut T,
}
impl<'a, T> Reborrow for InnerMut<'a, T> {}
#[derive(Clone, Copy)]
struct InnerRef<'a, T> {
value: &'a T,
}
impl<'a, T> CoerceShared<InnerRef<'a, T>> for InnerMut<'a, T> {}
struct OuterMut<'a, T> {
inner: InnerMut<'a, T>,
}
impl<'a, T> Reborrow for OuterMut<'a, T> {}
#[derive(Clone, Copy)]
struct OuterRef<'a, T> {
inner: InnerRef<'a, T>,
}
// New design: target.inner corresponds to source.inner.
// That field is not copied as-is; it recursively uses InnerMut: CoerceShared<InnerRef>.
impl<'a, T> CoerceShared<OuterRef<'a, T>> for OuterMut<'a, T> {}
```
<a id="example-s8-mut-to-shared-ref-leaves"></a>
### Example S8: `&mut T -> &T` leaves
**Old design:** Validation recognized this leaf, but CTFE/Miri still modeled shared reborrow as a copy/transmute.
**New design:** The leaf is a fresh shared reborrow of the mutable-reference referent.
**Implemented in:** `compiler/rustc_const_eval/src/interpret/step.rs`.
**Covered by:** `tests/ui/reborrow/coerce_shared_consteval.rs`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T> {
value: &'a mut T,
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
}
// Field leaf: source.value has type &mut T and target.value has type &T.
// Old CTFE/Miri behavior tried to copy an &mut T value into an &T destination.
// New CTFE/Miri behavior creates a fresh shared reborrow of *source.value.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s9-copy-compatible-leaves"></a>
### Example S9: Copy-compatible leaves
**Old design:** Same-type field copying was checked for the single accepted field.
**New design:** The same rule applies per leaf during field-wise validation and lowering.
**Implemented in:** `compiler/rustc_hir_analysis/src/coherence/builtin.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-multi-field.rs`, `tests/ui/reborrow/coerce-shared-generics.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T> {
value: &'a mut T,
tag: usize,
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
tag: usize,
}
// Old design: rejected because the wrapper has two data fields.
// New design: accepts two leaves:
// - value uses the &mut T -> &T shared-reborrow leaf.
// - tag is copied only because usize: Copy.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s10-validation-phase"></a>
### Example S10: Validation phase
**Old design:** Validation was simple and did not need to diagnose general field correspondence errors.
**New design:** Builtin impl validation reports user errors before borrowck, CTFE/Miri, or codegen see malformed field-wise `CoerceShared` MIR.
**Implemented in:** `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_hir_analysis/src/errors.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-missing-target-field.rs`, `tests/ui/reborrow/coerce-shared-field-relations.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T> {
value: &'a mut T,
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
missing: usize,
}
// Rejected in builtin impl validation:
// target.missing has no corresponding source field.
// Lower phases should not be responsible for turning this into a user diagnostic.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s11-borrowck-constraints"></a>
### Example S11: Borrowck constraints
**Old design:** Borrowck related selected fields but did not have the current target-driven recursive rule.
**New design:** Borrowck uses the canonical field pairs for constraints while issuing the loan for the whole source place.
**Implemented in:** `compiler/rustc_borrowck/src/borrow_set.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_borrowck/src/type_check/relate_tys.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Extra<'a, T> {
value: &'a mut T,
}
impl<'a, T> Reborrow for Extra<'a, T> {}
struct Mut<'a, T> {
value: &'a mut T,
extra: Extra<'a, T>, // source-only field
}
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
}
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
fn get<'a>(value: Ref<'a, i32>) -> &'a i32 {
value.value
}
fn demo(mut wrapped: Mut<'_, i32>) {
let shared = get(wrapped);
// Rejected by borrowck while `shared` is live:
// the loan is for the whole `wrapped` place, not just wrapped.value.
*wrapped.extra.value = 3;
let _ = shared;
}
```
<a id="example-s12-ctfe-miri-semantics"></a>
### Example S12: CTFE/Miri semantics
**Old design:** CTFE used a copy/transmute model for shared `Rvalue::Reborrow`.
**New design:** CTFE/Miri recursively reconstructs the target and creates fresh shared references for `&mut T -> &T` leaves.
**Implemented in:** `compiler/rustc_const_eval/src/interpret/step.rs`.
**Covered by:** `tests/ui/reborrow/coerce_shared_consteval.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
pub struct Mut<'a>(&'a mut u8);
impl Reborrow for Mut<'_> {}
#[derive(Clone, Copy)]
pub struct Ref<'a>(&'a u8);
impl<'a> CoerceShared<Ref<'a>> for Mut<'a> {}
const fn accept(_: Ref<'_>) {}
const fn demo() {
let mut value = 0;
// Old CTFE model: tried to copy/transmute Mut into Ref.
// New CTFE/Miri model: creates a fresh shared reborrow of *Mut.0.
accept(Mut(&mut value));
}
const _: () = demo();
```
<a id="example-s13-runtime-codegen-semantics"></a>
### Example S13: Runtime codegen semantics
**Old design:** Runtime codegen could read the source aggregate and write it as the target aggregate.
**New design:** Codegen must reconstruct the target fields in the target layout.
**Implemented in:** `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, `compiler/rustc_codegen_cranelift/src/base.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-reordered-field.rs`, `tests/ui/reborrow/coerce-shared-different-layout.rs`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a> {
x: &'a mut u8,
y: &'a mut u16,
}
impl<'a> Reborrow for Mut<'a> {}
#[derive(Clone, Copy)]
struct Ref<'a> {
y: &'a u16,
x: &'a u8,
}
// Old codegen assumption: source aggregate could be read and written as the target.
// That is wrong here because the target field order differs.
// New codegen: construct Ref { y: shared_reborrow(*source.y), x: shared_reborrow(*source.x) }.
impl<'a> CoerceShared<Ref<'a>> for Mut<'a> {}
```
<a id="example-s14-lower-phase-bug-malformed-mir-assumptions"></a>
### Example S14: Lower-phase `bug!` / malformed-MIR assumptions
**Old design:** Lower phases relied on narrow validation and asserted many assumptions directly.
**New design:** User-facing invalid cases should be rejected during typeck/impl validation; lower-phase failures mean malformed MIR or phase disagreement.
**Implemented in:** `compiler/rustc_borrowck/src/borrow_set.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, `compiler/rustc_codegen_cranelift/src/base.rs`.
**Covered by:** `tests/ui/reborrow/coerce-shared-missing-target-field.rs`, `tests/ui/reborrow/coerce-shared-foreign-private-field.rs`, issue regression tests under `tests/ui/reborrow/`.
```rust
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct Mut<'a, T>(&'a mut T);
impl<'a, T> Reborrow for Mut<'a, T> {}
#[derive(Clone, Copy)]
struct Ref<'a, T> {
value: &'a T,
}
// Rejected before lower phases:
// source is a tuple struct but target is a named-field struct.
// If borrowck, CTFE/Miri, or codegen sees MIR for this as a field-wise shared reborrow,
// that is malformed MIR / phase disagreement, not the normal diagnostic path.
impl<'a, T> CoerceShared<Ref<'a, T>> for Mut<'a, T> {}
```
<a id="example-s15-tests"></a>
### Example S15: Tests
**Old design:** Existing tests covered the narrow simple-wrapper model and earlier regressions.
**New design:** The new tests pin down each field-wise rule and the lower-phase semantics it depends on.
**Implemented in:** `tests/ui/reborrow/*`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs`.
**Covered by:** the test files listed in the comments below.
```rust
// Compact coverage map for this PR:
//
// Multi-field wrappers:
// - tests/ui/reborrow/coerce-shared-multi-field.rs
// - tests/ui/reborrow/coerce-shared-generics.rs
//
// Named reorder and tuple PhantomData filtered ordinal:
// - tests/ui/reborrow/coerce-shared-reordered-field.rs
// - tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs
//
// Source-only fields and whole-place borrowck loan:
// - tests/ui/reborrow/coerce-shared-different-layout.rs
// - tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs
//
// Nested wrappers and alias/projection normalization:
// - tests/ui/reborrow/coerce-shared-nested.rs
// - tests/ui/reborrow/coerce-shared-alias-projection.rs
//
// CTFE/Miri &mut T -> &T leaf semantics:
// - tests/ui/reborrow/coerce_shared_consteval.rs
// - src/tools/miri/tests/pass/reborrow-coerce-shared.rs
//
// User diagnostics before lower phases:
// - tests/ui/reborrow/coerce-shared-missing-target-field.rs
// - tests/ui/reborrow/coerce-shared-field-relations.rs
// - tests/ui/reborrow/coerce-shared-foreign-private-field.rs
// - tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs
```
## What exactly changed
### 1. Shared field correspondence helper
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| Helper location | No canonical multi-field correspondence helper. | `rustc_middle::ty::reborrow` defines `ReborrowField`, `CoerceSharedFieldPair`, and `coerce_shared_field_pairs`. |
| Named fields | No general shared rule. | Named structs match non-`PhantomData` fields by name. |
| Tuple fields | At most one data field, so ordinal matching was not a design point. | Tuple structs match non-`PhantomData` fields by filtered ordinal. |
| `PhantomData` | Ignored for old field count. | Ignored for matching, but original `FieldIdx` is preserved for projection. |
| Target fields | Equal count in old simple model. | Every target data field must have a source field. |
| Source-only fields | Rejected by equal-count rule. | Allowed; borrowck protects the whole source place. |
Files:
- `compiler/rustc_middle/src/ty/reborrow.rs`
- `compiler/rustc_middle/src/ty/mod.rs`
Why it changed: field-wise `CoerceShared` is only tractable if all phases agree on the same source-to-target field map. The helper intentionally does not fully normalize aliases; each phase applies its own normalization or type relation.
Tests:
- `tests/ui/reborrow/coerce-shared-multi-field.rs`
- `tests/ui/reborrow/coerce-shared-reordered-field.rs`
- `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs`
- `tests/ui/reborrow/coerce-shared-extra-marker.rs`
- `tests/ui/reborrow/coerce-shared-missing-target-field.rs`
### 2. HIR coercion shape guard
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| Adjustment creation | `CoerceShared` adjustment selection relied primarily on the trait obligation and later validation. | Before creating `GenericReborrow(Not)`, HIR typeck requires distinct ADT defs, struct source/target, one lifetime argument on both sides, and successful canonical field-pair shape. |
| Responsibility boundary | Shape assumptions could be discovered late. | HIR only creates MIR that lower phases know how to interpret structurally. Field-type legality remains with the `CoerceShared` obligation and builtin impl validation. |
Files:
- `compiler/rustc_hir_typeck/src/coercion.rs`
Why it changed: HIR should not create `Rvalue::Reborrow(_, Not, place)` for source/target shapes that borrowck, CTFE/Miri, or codegen cannot lower with the shared helper.
Tests:
- `tests/ui/reborrow/coerce-shared-missing-target-field.rs`
- `tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs`
- `tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs`
### 3. Builtin impl validation
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| Shape | Required one lifetime argument on each type and zero or one data field on each side. | Still requires the current single-lifetime model, but walks all canonical field pairs. |
| Field relations | The one field could be copy-compatible, recursively `CoerceShared`, or `&mut U -> &V`. | Each field pair must be copy-compatible, recursively `CoerceShared`, or a shared-reborrow leaf. |
| Missing fields | Equal data-field count avoided missing-source semantics. | Missing target source field is a user diagnostic. Source-only fields are accepted. |
| Field accessibility | Not part of the simple model. | Rejects inaccessible/private fields and applicable non-exhaustive field lists. |
| Diagnostics | Many bad cases could surface later or as generic obligations. | Adds targeted diagnostics for missing field, field-style mismatch, field mismatch, lifetime mismatch, and inaccessible fields. |
Files:
- `compiler/rustc_hir_analysis/src/coherence/builtin.rs`
- `compiler/rustc_hir_analysis/src/errors.rs`
Accepted field relations:
| Relation | Rule |
| --- | --- |
| Copy-compatible | Source and target field types are structurally equal after alias-aware relation, and the source field type implements `Copy`. |
| Recursive `CoerceShared` | Source and target field types are related by a `SourceField: CoerceShared<TargetField>` obligation. |
| Shared-reference leaf | Source field is `&mut U`, target field is `&V`, and pointee types are covariantly structurally related. |
Tests:
- `tests/ui/reborrow/coerce-shared-field-relations.rs`
- `tests/ui/reborrow/coerce-shared-wrong-generic.rs`
- `tests/ui/reborrow/coerce-shared-missing-target-field.rs`
- `tests/ui/reborrow/coerce-shared-foreign-private-field.rs`
- `tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs`
- `tests/ui/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs`
### 4. Borrowck
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| Loan | Shared `CoerceShared` became a shared loan of the source place. | This remains a whole-place shared loan of the original source place. |
| Source-only fields | Not supported by validation. | Protected by the whole-place loan even though they do not appear in the target. |
| Field constraints | Borrowck matched destination fields to source fields by name and skipped missing fields. | Borrowck recurses through canonical field pairs from `rustc_middle::ty::reborrow`. |
| Alias/projection relation | Normal borrowck relation did not structurally relate aliases for this path. | Adds `relate_types_structurally_relating_aliases` for shared reborrow field leaves. |
| Malformed MIR | Some assumptions used `unwrap()`/`bug!`. | Several paths now report `span_mirbug!` or delayed bugs for malformed MIR instead of panicking through incidental code. |
Files:
- `compiler/rustc_borrowck/src/borrow_set.rs`
- `compiler/rustc_borrowck/src/type_check/mod.rs`
- `compiler/rustc_borrowck/src/type_check/relate_tys.rs`
Why borrowck must agree with validation and lowering: borrowck adds the region/type constraints that justify the MIR operation. If borrowck relates a different set of fields than CTFE/Miri or codegen reconstructs, the compiler has a phase divergence bug.
Tests:
- `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs`
- `tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs`
- `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs`
- `tests/ui/reborrow/coerce-shared-alias-projection.rs`
### 5. MIR documentation / semantic comment
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| MIR comment | Shared `Rvalue::Reborrow` was described as a bitwise copy into a same-layout `CoerceShared` target ADT. | The comment now says generic shared reborrows are lowered field-wise according to validated `CoerceShared` source-to-target correspondence. |
| Lower-phase assumption | Lower phases could treat accepted shared cases as copy-like. | Lower phases may assume validated canonical correspondence for distinct ADTs; failure is malformed MIR. |
Files:
- `compiler/rustc_middle/src/mir/syntax.rs`
MIR meaning in this PR:
| Rvalue | Meaning |
| --- | --- |
| `Rvalue::Reborrow(target_ty, Mut, source)` | Generic exclusive reborrow of a same-ADT wrapper. |
| `Rvalue::Reborrow(target_ty, Not, source)` | Generic shared reborrow that reconstructs `target_ty` from `source` using validated `CoerceShared` field correspondence. |
### 6. CTFE / Miri
| Aspect | Old behavior | Problem | New behavior |
| --- | --- | --- | --- |
| Shared reborrow evaluation | `copy_op_allow_transmute` into the target place. | Different layouts and target reconstruction cannot be modeled as source-as-target copy. | `eval_reborrow_into_place` recursively writes target fields from source fields. |
| `&mut U -> &V` leaf | Fell through to typed copy. | Miri can reject copying `&mut ()` into `&()`, and the operation skips reference creation/retagging. | The leaf projects through `Deref` and writes a fresh shared reference via the same retag-aware path as `Rvalue::Ref`. |
| Aggregate state | Old copy path initialized the target as a single value. | Recursive field writes need target aggregate completion. | Writes the first-variant discriminant and validates the completed destination when validity is enforced. |
Files:
- `compiler/rustc_const_eval/src/interpret/step.rs`
Why this is semantic, not just an ICE workaround: the abstract operation for `&mut T -> &T` is a shared reborrow of the referent. CTFE/Miri must construct a fresh reference so validity and retag behavior are modeled. Runtime codegen may use the same pointer bits for the representation, but Miri cannot treat that as a typed copy of an `&mut` value.
Tests:
- `tests/ui/reborrow/coerce_shared_consteval.rs`
- `tests/ui/reborrow/coerce-shared-alias-projection.rs`
- `src/tools/miri/tests/pass/reborrow-coerce-shared.rs` is present and covers the Miri `&mut () -> &()` regression path, though it is not changed in this three-dot diff.
### 7. Runtime codegen
| Aspect | Old behavior | New behavior |
| --- | --- | --- |
| SSA codegen | `Rvalue::Reborrow` used `Operand::Copy(place)`, relying on old restrictions to make the operation copy-like. | Distinct ADT targets are reconstructed recursively in place or as operands. Memory-backed layouts use a scratch place. |
| Cranelift codegen | Read the source place as a value and wrote it to the destination. | Recursively reconstructs distinct target ADTs from canonical field pairs. |
| Reference leaves | Copying pointer bits can be representation-compatible. | That is an implementation of the abstract shared-reborrow semantics, not the semantic definition. |
Files:
- `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`
- `compiler/rustc_codegen_cranelift/src/base.rs`
Tests:
- Run-pass UI tests exercise runtime codegen for named reorder, tuple marker filtering, multi-field metadata, nested wrappers, generics, and source-only fields.
- `tests/ui/reborrow/coerce-shared-alias-projection.rs`
- `tests/ui/reborrow/coerce-shared-different-layout.rs`
- `tests/ui/reborrow/coerce-shared-multi-field.rs`
- `tests/ui/reborrow/coerce-shared-nested.rs`
### 8. Tests
| Design point | Tests |
| --- | --- |
| Multi-field field relations | `tests/ui/reborrow/coerce-shared-multi-field.rs`, `tests/ui/reborrow/coerce-shared-generics.rs` |
| Named reorder | `tests/ui/reborrow/coerce-shared-reordered-field.rs` |
| Tuple `PhantomData` filtered position | `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs` |
| Extra marker fields | `tests/ui/reborrow/coerce-shared-extra-marker.rs`, `tests/ui/reborrow/marker-coerce-shared-corrected-issue-156309.rs` |
| Missing target/source field diagnostics | `tests/ui/reborrow/coerce-shared-missing-target-field.rs`, `tests/ui/reborrow/coerce-shared-field-relations.rs` |
| Source-only fields | `tests/ui/reborrow/coerce-shared-different-layout.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs` |
| Nested wrappers | `tests/ui/reborrow/coerce-shared-nested.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs` |
| Generics | `tests/ui/reborrow/coerce-shared-generics.rs`, `tests/ui/reborrow/coerce-shared-wrong-generic.rs` |
| Lifetime mismatch / single-lifetime restriction | `tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs`, `tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs` |
| Alias/projection | `tests/ui/reborrow/coerce-shared-alias-projection.rs` |
| CTFE mutable-to-shared | `tests/ui/reborrow/coerce_shared_consteval.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs` |
| Miri `&mut () -> &()` regression | `src/tools/miri/tests/pass/reborrow-coerce-shared.rs` present, unchanged in this diff |
| Private/foreign fields | `tests/ui/reborrow/coerce-shared-foreign-private-field.rs`, `tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs`, `tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs` |
| Old issue regressions | `tests/ui/reborrow/malformed-marker-coerce-shared-issue-156309.rs`, `tests/ui/reborrow/marker-coerce-shared-corrected-issue-156309.rs`, `tests/ui/reborrow/missing-generic-args-coerce-shared-issue-156315.rs`, `tests/ui/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs` |
## Design invariant summary
1. There is exactly one canonical field correspondence for `CoerceShared`.
2. Validation, borrowck, CTFE/Miri, and codegen must use the same correspondence.
3. Target reconstruction is target-driven: every target data field must be produced from a corresponding source field.
4. Source-only fields are allowed, but the original source place is borrowed as a whole.
5. `&mut T -> &T` is a fresh shared reborrow leaf, not a typed copy.
6. Lower-phase helper failures are malformed-MIR invariants, not user diagnostics.
7. User-facing invalid cases should be rejected during impl validation/typeck.
## Why the new design is desirable
| Need | Why the old design is insufficient | What the new design enables |
| --- | --- | --- |
| Different mutable/shared ADTs | Same-layout/copy-like assumptions reject or mis-lower different shared views. | The target can be reconstructed from corresponding source fields. |
| Metadata and marker fields | A one-field model cannot represent realistic wrappers with metadata. | Multiple copy-compatible metadata fields can be carried into the shared view. |
| Source-only fields | Equal data-field count forces all source state into the shared target. | Mutable-only bookkeeping can remain in the source while the shared target omits it. |
| Natural Rust field organization | Named reorder and tuple marker fields are common. | Named matching and tuple filtered ordinal matching avoid artificial layout constraints. |
| Nested wrapper composition | A single top-level field check does not define recursive semantics. | Nested `CoerceShared` wrappers compose through recursive reconstruction. |
| Phase agreement | Ad hoc matching in each phase risks validation/borrowck/codegen/CTFE divergence. | One canonical helper drives all lower phases. |
| CTFE/Miri correctness | Typed copy is not a shared reborrow for `&mut T -> &T`. | CTFE/Miri creates fresh shared-reference leaves. |
## Costs and complexity added
| Cost | Why it matters |
| --- | --- |
| More complicated MIR semantics | Shared `Rvalue::Reborrow` now means target reconstruction, not just copy-like value production. |
| More lower-phase code | Borrowck, CTFE/Miri, SSA codegen, and Cranelift each need recursive lowering. |
| Need for shared helper | A central helper is now required to prevent phase divergence. |
| Potential user-reachable ICEs if phases disagree | If HIR/validation accept a case that a lower phase cannot interpret, lower phases still contain malformed-MIR assertions. |
| More tests needed | Each rule needs coverage across validation, borrowck, CTFE/Miri, and runtime codegen. |
## Alternatives considered
### Alternative A: keep old same-layout / simple-wrapper model
| Benefit | Problem |
| --- | --- |
| It is much simpler: validation can keep zero/one data fields and lower phases can remain copy-like. | It rejects the wrappers this PR targets: multi-field wrappers, metadata-bearing wrappers, source-only fields, named reorder, tuple marker fields, and nested shared views. It also leaves the CTFE/Miri `&mut T -> &T` typed-copy hole unresolved for field leaves. |
### Alternative B: require source and target to have exactly matching data fields
| Benefit | Problem |
| --- | --- |
| It avoids source-only field semantics and makes correspondence easier to reason about. | It rejects useful mutable wrappers whose shared view intentionally omits mutable-only state or metadata. It also does not by itself solve tuple `PhantomData` filtered ordinals or fresh-reference leaf semantics. |
### Alternative C: lower to explicit aggregate construction plus reference reborrows before MIR consumers
| Benefit | Problem |
| --- | --- |
| It may make MIR semantics more explicit and reduce special recursive handling in CTFE/codegen. | It requires a different implementation shape and a decision about where this lowering belongs. It also needs careful treatment of borrowck loans for source-only fields. |
## PR implementation map
| Design component | Old behavior | New behavior | File(s) | Test coverage |
| --- | --- | --- | --- | --- |
| MIR semantics comment | Shared generic reborrow described as bitwise/same-layout copy. | Shared generic reborrow described as field-wise lowering from validated correspondence. | `compiler/rustc_middle/src/mir/syntax.rs` | Covered indirectly by all `Rvalue::Reborrow(Not)` tests. |
| Canonical field helper | No shared multi-field helper. | Adds field-pair helper, field structs, and pair errors. | `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_middle/src/ty/mod.rs` | `coerce-shared-multi-field.rs`, `coerce-shared-reordered-field.rs`, `coerce-shared-tuple-phantom-position.rs`, `coerce-shared-missing-target-field.rs` |
| HIR shape guard | `GenericReborrow(Not)` could be considered before a shared structural shape check. | Requires distinct struct ADTs, one lifetime argument on both types, and successful field-pair helper. | `compiler/rustc_hir_typeck/src/coercion.rs` | `coerce-shared-marker-no-target-lifetime.rs`, `coerce-shared-missing-target-field.rs` |
| Builtin impl validation | Zero or one data field on each side. | Validates every canonical field pair. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs` | `coerce-shared-field-relations.rs`, `coerce-shared-wrong-generic.rs`, `corrected-field-mismatch-coerce-shared-issue-156315.rs` |
| Diagnostics | Existing lifetime/multi-field errors only. | Adds field mismatch, missing field, field-style mismatch, and inaccessible-field diagnostics. | `compiler/rustc_hir_analysis/src/errors.rs` | `.stderr` files for field relations, missing fields, wrong generic, lifetime mismatch, marker target lifetime, foreign/private fields |
| Borrow set | Shared loan of source place, but non-ADT/lifetime assumptions used harder assertions. | Keeps whole-place shared loan and converts some malformed assumptions to delayed bugs. | `compiler/rustc_borrowck/src/borrow_set.rs` | `coerce-shared-omitted-reborrow-field-locked.rs` |
| Borrowck type constraints | Field-by-name relation, skipped missing fields, no recursion through canonical helper. | Recursive constraints over canonical field pairs; source-only fields remain protected by whole-place loan. | `compiler/rustc_borrowck/src/type_check/mod.rs` | `coerce-shared-omitted-reborrow-field*.rs`, `coerce-shared-nested.rs` |
| Borrowck alias relation | Normal borrowck relation left aliases opaque for this path. | Adds alias-aware structural relation for shared reborrow field constraints. | `compiler/rustc_borrowck/src/type_check/relate_tys.rs` | `coerce-shared-alias-projection.rs` |
| CTFE/Miri shared lowering | Shared reborrow used transmute-like copy. | Recursively reconstructs target; `&mut U -> &V` leaf creates a fresh shared reference. | `compiler/rustc_const_eval/src/interpret/step.rs` | `coerce_shared_consteval.rs`, `coerce-shared-alias-projection.rs`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs` |
| SSA direct-place codegen | Reborrow fell through to operand copy. | Direct-place recursive reconstruction for distinct ADTs. | `compiler/rustc_codegen_ssa/src/mir/rvalue.rs` | Run-pass field-wise UI tests |
| SSA operand codegen | Operand copy assumed source-as-target. | Builds aggregate operands recursively or uses scratch place for memory-backed target layouts. | `compiler/rustc_codegen_ssa/src/mir/rvalue.rs` | `coerce-shared-different-layout.rs`, `coerce-shared-multi-field.rs` |
| Cranelift codegen | Read source place as value and wrote destination. | Recursive distinct-ADT reconstruction. | `compiler/rustc_codegen_cranelift/src/base.rs` | Run-pass field-wise UI tests |
Supporting test files:
| File | Purpose |
| --- | --- |
| `tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs` | Auxiliary crate with private/tuple private fields for accessibility diagnostics. |
| `tests/ui/reborrow/coerce-shared-foreign-private-field.rs` | Rejects named private foreign target fields. |
| `tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs` | Rejects tuple private foreign target fields. |
| `tests/ui/reborrow/malformed-marker-coerce-shared-issue-156309.rs` | Keeps the original malformed marker diagnostic path covered. |
| `tests/ui/reborrow/marker-coerce-shared-corrected-issue-156309.rs` | Corrected marker-only `CoerceShared` pass case. |
| `tests/ui/reborrow/missing-generic-args-coerce-shared-issue-156315.rs` | Original generic-args error plus field mismatch diagnostic path. |
| `tests/ui/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs` | Isolated corrected field-mismatch regression. |
## Why this PR is desirable
This PR is desirable because it makes concrete a direction that already exists in the reborrow design and in the compiler's own comments. It is not a general argument for reborrows. The point is narrower: the old implementation treated shared `CoerceShared` reborrows as simple same-layout/copy-like value production, while the design text and several in-tree comments already pointed toward field-wise, type-adjusting reconstruction.
## Alignment with the pre-RFC direction
The pre-RFC presents `Reborrow` and `CoerceShared` as the mechanism that lets user-defined exclusive reference-like wrappers behave like references at coercion sites. It also says shared reborrowing of an exclusive wrapper should produce the shared view while the source remains usable after the shared reborrow ends. This PR follows that model by keeping `Rvalue::Reborrow(_, Not, source)` as a shared loan of the source place in `compiler/rustc_borrowck/src/borrow_set.rs` and by relating the target lifetime to the source in `compiler/rustc_borrowck/src/type_check/mod.rs`.
The pre-RFC is stronger than the old compiler comments in one important respect: it explicitly describes constructing the `CoerceShared` target from target fields, copying compatible fields, allowing `&mut T -> &T`, and recursing through non-`Copy` reborrowable fields. It also gives examples where the shared target has a different shape, such as an `ImbrisMut` source with metadata omitted from `ImbrisRef`, and multi-field wrappers such as `MatMut`/`MatRef`. This PR makes an implementation of that direction for the current single-lifetime model through `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, and `compiler/rustc_codegen_cranelift/src/base.rs`.
There is also a later design comment in the pre-RFC discussion that is directly aligned with this PR: type-adjusting reborrowing can be checked by the compiler without requiring shared layout, by checking fields name-wise, applying privacy checks at the impl scope, and ignoring `PhantomData` fields. This PR implements that named-field direction through the canonical field-pair helper in `compiler/rustc_middle/src/ty/reborrow.rs`, field access validation in `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, and the HIR shape guard in `compiler/rustc_hir_typeck/src/coercion.rs`. For tuple structs, the PR makes the analogous compiler rule explicit by using filtered ordinal matching in `compiler/rustc_middle/src/ty/reborrow.rs`.
This is still not a claim that the PR fully implements the pre-RFC. The pre-RFC discusses broader cases such as multiple lifetimes. This PR makes explicit an extension that the old in-tree implementation did not fully spell out: field-wise `CoerceShared` for the current one-reborrow-lifetime compiler model.
## Existing in-tree comments / FIXMEs this PR resolves
| Pre-PR comment / FIXME | Location before this PR | What it implied was missing | How this PR addresses it |
| --- | --- | --- | --- |
| Shared `Rvalue::Reborrow` created a bitwise copy, and the `CoerceShared` target was "currently" required to have the same memory layout; changing layout was listed as future work. | `compiler/rustc_middle/src/mir/syntax.rs` at merge base `59bc26c`, around the `Rvalue::Reborrow` comment. | MIR semantics did not yet describe target reconstruction for different-layout shared targets. | Updates the MIR comment in `compiler/rustc_middle/src/mir/syntax.rs` to field-wise lowering from validated correspondence, with lowering implemented in CTFE and codegen. Multiple lifetimes remain outside this PR. |
| `CoerceShared` validation required the same number of data fields, not more than one, while noting the eventual intention to support multiple fields and let the shared target omit fields from the source. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs` at merge base `59bc26c`, around the old `coerce_shared_info` shape check. | Impl validation had no general target-driven field correspondence rule. | Replaces the one-field check with `coerce_shared_field_pairs` and `validate_coerce_shared_fields` in `compiler/rustc_middle/src/ty/reborrow.rs` and `compiler/rustc_hir_analysis/src/coherence/builtin.rs`. |
| Old validation said one data field on each side must be the same `Copy` type or relate through `CoerceShared`. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs` at merge base `59bc26c`, around the old single-field validation path. | The relation existed only for the single accepted field pair. | Applies the same relation per canonical field pair in `validate_coerce_shared_field` in `compiler/rustc_hir_analysis/src/coherence/builtin.rs`. |
| `&mut T` was treated as implementing `CoerceShared` to `&T`, "except not really." | `compiler/rustc_hir_analysis/src/coherence/builtin.rs` at merge base `59bc26c`, in the old single-field special case. | The leaf was recognized by validation, but lower phases still needed to treat it as an actual shared reborrow operation. | Keeps the validation leaf in `compiler/rustc_hir_analysis/src/coherence/builtin.rs` and gives CTFE/Miri a fresh shared-reference leaf path in `compiler/rustc_const_eval/src/interpret/step.rs`. |
| `FIXME(#155345): This should return Unnormalized` sat on the helper that collected non-`PhantomData` fields. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs` at merge base `59bc26c`, above `collect_struct_data_fields`. | Field collection was local to validation and did not provide a shared phase-aware correspondence model. | Replaces the local helper with `reborrow_data_fields` and `coerce_shared_field_pairs` in `compiler/rustc_middle/src/ty/reborrow.rs`. Field types are still instantiated with phase-specific normalization/alias handling done by each consumer, so this addresses the phase-sharing problem rather than implementing an `Unnormalized` return type literally. |
| `FIXME(reborrow)` said borrowck needed to relate `CoerceShared` manually, field by field, because source and target ADTs are generally unrelated. | `compiler/rustc_borrowck/src/type_check/mod.rs` at merge base `59bc26c`, in `add_generic_reborrow_constraint`. | Borrowck did not yet have recursive field-wise type and region constraints for shared `CoerceShared`. | Adds `add_generic_shared_reborrow_constraints` in `compiler/rustc_borrowck/src/type_check/mod.rs`, using the canonical field pairs and alias-aware structural relation from `compiler/rustc_borrowck/src/type_check/relate_tys.rs`. |
| The old borrowck code matched destination fields to source fields by name and silently skipped missing source fields. | `compiler/rustc_borrowck/src/type_check/mod.rs` at merge base `59bc26c`, immediately after the `FIXME(reborrow)`. | Borrowck's field matching was ad hoc and not the same target-driven rule needed by validation and lowering. | Uses `coerce_shared_field_pairs` in `compiler/rustc_borrowck/src/type_check/mod.rs`; missing target fields are rejected during validation in `compiler/rustc_hir_analysis/src/coherence/builtin.rs` and diagnosed by `compiler/rustc_hir_analysis/src/errors.rs`. |
| CTFE said shared generic reborrows use `CoerceShared` as a bitwise copy into a distinct same-layout target ADT, implemented with `copy_op_allow_transmute`. | `compiler/rustc_const_eval/src/interpret/step.rs` at merge base `59bc26c`, in the old `Reborrow` arm. | Interpretation could not model different-layout target reconstruction or fresh `&mut T -> &T` shared leaves. | Adds recursive `eval_reborrow_into_place` and `write_ref_to_place` in `compiler/rustc_const_eval/src/interpret/step.rs`. |
| SSA codegen noted shared reborrowing was not necessarily a simple memcpy, but old coherence restrictions guaranteed it was, then lowered `Rvalue::Reborrow` as `Operand::Copy(place)`. | `compiler/rustc_codegen_ssa/src/mir/rvalue.rs` at merge base `59bc26c`, in the old `codegen_rvalue_operand` arm. | Runtime codegen had no path for reconstructed targets once validation stopped guaranteeing copy-like layout. | Adds recursive `codegen_reborrow_into`, `codegen_reborrow_operand`, and field-pair projection in `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, with analogous Cranelift reconstruction in `compiler/rustc_codegen_cranelift/src/base.rs`. |
## Desired behavior this PR makes possible
| Desired behavior | Why old design was insufficient | Implemented in | Covered by |
| --- | --- | --- | --- |
| Multiple corresponding data fields. | Validation accepted zero or one non-`PhantomData` data field, so metadata-bearing wrappers could not be validated or lowered field-wise. | `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, `compiler/rustc_codegen_cranelift/src/base.rs`. | `tests/ui/reborrow/coerce-shared-multi-field.rs`, `tests/ui/reborrow/coerce-shared-generics.rs`. |
| Named field reordering. | With at most one data field, the old design did not need to define multi-field named correspondence. | `compiler/rustc_middle/src/ty/reborrow.rs`, consumed by validation, borrowck, CTFE, and codegen. | `tests/ui/reborrow/coerce-shared-reordered-field.rs`. |
| Tuple marker fields and `PhantomData` ignored for matching while preserving real field indices. | Old validation ignored `PhantomData` for counting, but there was no reusable filtered-ordinal model for tuple fields or original-index projection. | `compiler/rustc_middle/src/ty/reborrow.rs`. | `tests/ui/reborrow/coerce-shared-tuple-phantom-position.rs`, `tests/ui/reborrow/coerce-shared-extra-marker.rs`. |
| Source-only fields with whole-place borrow protection. | Old validation required equal data-field counts in the simple model, so a mutable wrapper could not omit mutable-only state from the shared target. | `compiler/rustc_middle/src/ty/reborrow.rs`, `compiler/rustc_borrowck/src/borrow_set.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`. | `tests/ui/reborrow/coerce-shared-different-layout.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs`, `tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs`. |
| Nested wrapper fields through recursive `CoerceShared`. | A single top-level field check did not define recursive validation, borrowck constraints, CTFE reconstruction, or runtime reconstruction. | `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, `compiler/rustc_codegen_cranelift/src/base.rs`. | `tests/ui/reborrow/coerce-shared-nested.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs`. |
| Alias/projection field relations. | Normal borrowck relation did not structurally relate aliases for this path, which could diverge from validation/lowering. | `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_borrowck/src/type_check/relate_tys.rs`, plus phase-specific normalization in CTFE/codegen. | `tests/ui/reborrow/coerce-shared-alias-projection.rs`. |
| CTFE/Miri `&mut T -> &T` leaves as fresh shared reborrows. | Typed copy/transmute of an `&mut T` value into an `&T` destination misses the reference-creation operation and retag/validity behavior. | `compiler/rustc_const_eval/src/interpret/step.rs`. | `tests/ui/reborrow/coerce_shared_consteval.rs`, `tests/ui/reborrow/coerce-shared-alias-projection.rs`, `src/tools/miri/tests/pass/reborrow-coerce-shared.rs`. |
| Consistent validation, borrowck, CTFE, SSA codegen, and Cranelift field correspondence. | Old logic was split: validation had one-field checks, borrowck had ad hoc name matching, CTFE copied/transmuted, and codegen copied the source as the target. | `compiler/rustc_middle/src/ty/reborrow.rs` as the shared helper, consumed by `compiler/rustc_hir_typeck/src/coercion.rs`, `compiler/rustc_hir_analysis/src/coherence/builtin.rs`, `compiler/rustc_borrowck/src/type_check/mod.rs`, `compiler/rustc_const_eval/src/interpret/step.rs`, `compiler/rustc_codegen_ssa/src/mir/rvalue.rs`, and `compiler/rustc_codegen_cranelift/src/base.rs`. | Field-wise UI tests under `tests/ui/reborrow/`, especially `coerce-shared-field-relations.rs`, `coerce-shared-missing-target-field.rs`, `coerce-shared-multi-field.rs`, `coerce-shared-reordered-field.rs`, `coerce-shared-tuple-phantom-position.rs`, and `coerce-shared-alias-projection.rs`. |
## Why this is not arbitrary structural coercion
This PR does not allow arbitrary unrelated ADTs to coerce. HIR only creates the shared generic reborrow adjustment after checking that the source and target are distinct struct ADTs, each still fits the current single-lifetime model, the canonical field-pair shape succeeds, and the `CoerceShared` obligation holds in `compiler/rustc_hir_typeck/src/coercion.rs`.
The real gate remains builtin `CoerceShared` impl validation in `compiler/rustc_hir_analysis/src/coherence/builtin.rs`. The field map is narrow and canonical: named structs match non-`PhantomData` fields by name; tuple structs match non-`PhantomData` fields by filtered ordinal; original `FieldIdx` values are preserved for projection; and every target data field must be produced from a source data field in `compiler/rustc_middle/src/ty/reborrow.rs`.
The accepted field relations are also narrow. A field pair must be copy-compatible and `Copy`, recursively related by `CoerceShared`, or a shared-reference leaf from `&mut T` to `&T`, as checked in `compiler/rustc_hir_analysis/src/coherence/builtin.rs` and mirrored by borrowck in `compiler/rustc_borrowck/src/type_check/mod.rs`. Invalid missing-field, wrong-field, lifetime, and inaccessible-field cases are rejected by validation/typeck with diagnostics in `compiler/rustc_hir_analysis/src/errors.rs` and tests under `tests/ui/reborrow/`.
Source-only fields do not become unprotected just because they are omitted from the target. The loan remains a shared loan of the original source place in `compiler/rustc_borrowck/src/borrow_set.rs`, and `compiler/rustc_borrowck/src/type_check/mod.rs` documents that source-only fields remain protected for the inferred target lifetime.
## Remaining design review still needed
This section explains why the PR is desirable; it does not claim that the design is already accepted or that reviewer/design approval is unnecessary.
Reviewers still need to decide whether field-wise `CoerceShared` is the right extension of the current implementation, whether target-driven reconstruction and source-only fields are acceptable, and whether this should remain part of `Rvalue::Reborrow` semantics or be lowered to explicit MIR aggregate construction and reference reborrows earlier.