Skip to content

Reconsider Rule 4E (early) for RFC 3627 #130501

Open

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    A-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.T-langRelevant to the language team, which will review and decide on the PR/issue.Relevant to the language team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions