rust-lang / rust-lang/rust

Reconsider Rule 4E (early) for RFC 3627

Open
#130,501 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-patterns C-discussion T-lang
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Context

RFC3627 set out to improve some unintuitive edges of match ergonomics. The subtlest part involves fixing this case:

let [x]: &[&T] = ...;
// x: &&T
let [&x]: &[&T] = ...;
// x: T

where a & pattern appears to remove two layers of references. T-lang agreed on the desireability of "eat-one-layer" instead, namely that a & pattern should only ever remove one layer of reference.

For that, the RFC proposes rule 2: "When a reference pattern matches against a reference, do not update the default binding mode". While this is arguably a straightforward change from an implementation perspective, let me show you that it does not appropriately solve the problem we set out to solve from a language perspective.

Issue

The reason is simple: there are two references at play in &&T; with rule 2 we match the pattern against the inner one of these. Some consequences (you can try these out in my online tool which can run both TC's and my solvers; just note that rule4-early has bugs when combined with rule 5):

  • The mutability that matters is the inner one:
let [x]: &[&mut T] = ...;
// x: &&mut T
let [&x]: &[&mut T] = ...;
// with rule 2: Type error
// with rule 4E: x: &mut T + borrow checking error
let [&mut x]: &[&mut T] = ...;
// with rule 2: x: &T
// with rule 4E: type error

let [x]: &mut [&T] = ...;
// x: &mut &T
let [&x]: &mut [&T] = ...;
// with rule 2: `x: &mut T`, which causes a borrow-checking error
// with rule 4E: type error
let [&mut x]: &mut [&T] = ...;
// with rule 2: Type error
// with rule 4E: x: &T
  • References are considered inherited when they shouldn't, which is visible with mut or ref bindings:
let [&x]: &[&T] = ...;
// with rule 2: x: &T
// with rule 4E: x: &T
let [&ref x]: &[&T] = ...;
// with rule 2: x: &T because the reference was considered inherited and `ref x` overrides that
// with rule 4E: x: &&T

let &[x]: &[&T] = ...;
// with rule 2: x: &T
// with rule 4E: x: &T
let &[ref x]: &[&T] = ...;
// with rule 2: x: &&T because the reference was not considered inherited
// with rule 4E: x: &&T
  • Combined with the rest of RFC3627, we get weirdly inconsistent behaviors such as:
let [&mut (ref x)]: &mut [&mut T] = ...;
// RFC3627: `x: &T` because we got an inherited `&mut` and `ref` overrode it
// with rule 4E: x: &&mut T
let [&mut (ref x)]: &mut [&    T] = ...;
// RFC3627: `x: &&T` because the mutability mismatch triggered rule 4 instead
// with rule 4E: x: &&T

In short: rule 2 does "eat-one-layer" but eats the wrong layer. The fix is simply to eat the other one. In the language of RFC3627: "when the binding mode is ref or ref mut, match the pattern against the binding mode as if it was a reference"; this has been called "rule 4-early" in our discussions.

Edition

RFC3627 proposed to enable rules 1 and 2 over the edition. I propose to instead enable rules 1 and 4-early over the edition. Note that rule 4-early also replaces rule 4.

While I'm at it I would like to add a small additional rule, to enable fixing all the surprises:

  • Rule 1.5: When the DBM (default binding mode) is not move, writing ref on a binding is an error.

We can always revert to the previous behavior (i.e. ref x swallows a reference if it is inherited) if we wish to later.

cc @traviscross

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading RFC 3627 and the rule 2, rule 4-early, and rule 1.5 discussion in this issue. Use the linked online tool and the listed Rust examples to compare the proposed binding-mode behavior; done means reaching agreement on the language rules and their edition impact.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.