Summary

The current compiler implements

実装する
a more expansive semantics for pattern matching
一致する、マッチさせる
than was originally intended. This RFC introduces several mechanisms
仕組み、機構
to reign in these semantics without actually breaking (much, if any) extant code:

  • Introduce
    導入する
    a feature-gated attribute #[structural_match] which can be applied
    適用する
    to a struct
    構造、構造体
    or enum T to indicate
    指し示す
    that constants
    定数
    of type T can be used within patterns.
  • Have #[derive(Eq)] automatically
    自動的に
    apply
    適用する
    this attribute to the struct
    構造、構造体
    or enum that it decorates. Automatically
    自動的に
    inserted attributes do not require use of feature-gate.
  • When expanding constants
    定数
    of struct
    構造、構造体
    or enum type into equivalent
    等価
    patterns, require that the struct
    構造、構造体
    or enum type is decorated with #[structural_match]. Constants
    定数
    of builtin types are always expanded.

The practical effect of these changes will be to prevent

防ぐ
the use of constants
定数
in patterns unless the type of those constants
定数
is either a built-in
組み込みの
type (like i32 or &str) or a user-defined constant
定数
for which Eq is derived (not merely implemented
実装する
).

To be clear, this #[structural_match] attribute is never intended to be stabilized. Rather, the intention of this change is to restrict

制限する
constant
定数
patterns to those cases that everyone can agree on for now. We can then have further
さらなる、それ以上
discussion to settle the best semantics in the long term.
項、用語

Because the compiler currently accepts

受け付ける、受理する
arbitrary
任意の
constant
定数
patterns, this is technically a backwards incompatible change. However, the design
設計(する)
of the RFC means that existing code that uses constant
定数
patterns will generally "just work". The justification for this change is that it is clarifying "underspecified language
言語
semantics" clause,
句、節
as described
記述する
in RFC 1122
. A recent crater run with a prototype implementation
実装
found 6 regressions.

Note: this was also discussed on an internals thread. Major points from that thread are summarized either inline or in alternatives.

Motivation

The compiler currently permits

許す
any kind of constant
定数
to be used within a pattern. However, the meaning of such a pattern is somewhat controversial: the current semantics implemented
実装する
by the compiler were adopted in July of 2014 and were never widely discussed nor did they go through the RFC process. Moreover,
その上
the discussion at the time was focused primarily on implementation
実装
concerns, and overlooked the potential semantic hazards.

Semantic vs structural equality
同一性、等式

Consider

考える、みなす
a program like this one, which references
参照する
a constant
定数
value from within a pattern:

#![allow(unused)] fn main() { struct SomeType { a: u32, b: u32, } const SOME_CONSTANT: SomeType = SomeType { a: 22+22, b: 44+44 }; fn test(v: SomeType) { match v { SOME_CONSTANT => println!("Yes"), _ => println!("No"), } } }

The question at hand is what do we expect this match

一致する、マッチさせる
to do, precisely? There are two main possibilities: semantic and structural equality.
同一性、等式

Semantic equality.

同一性、等式
Semantic equality
同一性、等式
states that a pattern SOME_CONSTANT matches
一致する、マッチさせる
a value v if v == SOME_CONSTANT. In other words, the match
一致する、マッチさせる
statement
above would be exactly
正確に
equivalent
等価
to an if:

#![allow(unused)] fn main() { if v == SOME_CONSTANT { println!("Yes") } else { println!("No"); } }

Under semantic equality,

同一性、等式
the program above would not compile, because SomeType does not implement
実装する
the PartialEq trait.

Structural equality.

同一性、等式
Under structural equality,
同一性、等式
v matches
一致する、マッチさせる
the pattern SOME_CONSTANT if all of its fields are (structurally) equal.
等しい
Primitive types like u32 are structurally equal
等しい
if they represent the same value (but see below for discussion about floating point types like f32 and f64). This means that the match
一致する、マッチさせる
statement
above would be roughly equivalent
等価
to the following if (modulo privacy):

#![allow(unused)] fn main() { if v.a == SOME_CONSTANT.a && v.b == SOME_CONSTANT.b { println!("Yes") } else { println!("No"); } }

Structural equality

同一性、等式
basically says "two things are structurally equal
等しい
if their fields are structurally equal".
等しい
It is sort of equality
同一性、等式
you would get if everyone used #[derive(PartialEq)] on all types. Note that the equality
同一性、等式
defined
定義する
by structural equality
同一性、等式
is completely distinct
区別された/独立した
from the == operator,
演算子
which is tied to the PartialEq traits. That is, two values that are semantically
意味論的に
unequal
could be structurally equal
等しい
(an example where this might occur
起こる
is the floating point value NaN).

Current semantics. The compiler's current semantics are basically structural equality,

同一性、等式
though in the case of floating point numbers they are arguably closer to semantic equality
同一性、等式
(details below). In particular, when a constant
定数
appears
現れる
in a pattern, the compiler first evaluates
評価する(される)
that constant
定数
to a specific
特定の
value. So we would reduce the expression:

#![allow(unused)] fn main() { const SOME_CONSTANT: SomeType = SomeType { a: 22+22, b: 44+44 }; }

to the value SomeType { a: 44, b: 88 }. We then expand the pattern SOME_CONSTANT as though you had typed this value in place (well, almost as though, read on for some complications around privacy). Thus

それゆえに、従って、
the match
一致する、マッチさせる
statement
above is equivalent
等価
to:

#![allow(unused)] fn main() { match v { SomeType { a: 44, b: 88 } => println!(Yes), _ => println!("No"), } }

Disadvantages of the current approach

Given

与えられた
that the compiler already has a defined
定義する
semantics, it is reasonable to ask why we might want to change it. There are two main disadvantages:

  1. No abstraction boundary. The current approach does not permit
    許す
    types to define
    定義する
    what equality
    同一性、等式
    means for themselves (at least not if they can be constructed
    作る、構成体
    in a constant).
  2. Scaling
    規模を変更する
    to associated constants.
    定数
    The current approach does not permit
    許す
    associated constants
    定数
    or generic integers
    整数
    to be used in a match
    一致する、マッチさせる
    statement.

Disadvantage: Weakened abstraction bounary

The single

単一の
biggest concern with structural equality
同一性、等式
is that it introduces two distinct
区別された/独立した
notions of equality:
同一性、等式
the == operator,
演算子
based
基となる、基底(の)
on the PartialEq trait, and pattern matching,
一致する、マッチさせる
based
基となる、基底(の)
on a builtin structural recursion. This will cause
起こす
problems for user-defined types that rely on PartialEq to define
定義する
equality.
同一性、等式
Put another way, it is no longer possible for user-defined types to completely define
定義する
what equality
同一性、等式
means for themselves
(at least not if they can be constructed
作る、構成体
in a constant). Furthermore, because the builtin structural recursion does not consider
考える、みなす
privacy, match
一致する、マッチさせる
statements
can now be used to observe private fields.

Example: Normalized durations. Consider

考える、みなす
a simple duration type:

#![allow(unused)] fn main() { #[derive(Copy, Clone)] pub struct Duration { pub seconds: u32, pub minutes: u32, } }

Let's say that this Duration type wishes to represent a span of time, but it also wishes to preserve whether that time was expressed in seconds or minutes. In other words, 60 seconds and 1 minute are equal

等しい
values, but we don't want to normalize 60 seconds into 1 minute; perhaps because it comes from user input and we wish to keep things just as the user chose to express it.

We might implement

実装する
PartialEq like so (actually the PartialEq trait is slightly different, but you get the idea):

#![allow(unused)] fn main() { impl PartialEq for Duration { fn eq(&self, other: &Duration) -> bool { let s1 = (self.seconds as u64) + (self.minutes as u64 * 60); let s2 = (other.seconds as u64) + (other.minutes as u64 * 60); s1 == s2 } } }

Now imagine I have some constants:

定数

#![allow(unused)] fn main() { const TWENTY_TWO_SECONDS: Duration = Duration { seconds: 22, minutes: 0 }; const ONE_MINUTE: Duration = Duration { seconds: 0, minutes: 1 }; }

And I write a match

一致する、マッチさせる
statement
using those constants:
定数

#![allow(unused)] fn main() { fn detect_some_case_or_other(d: Duration) { match d { TWENTY_TWO_SECONDS => /* do something */, ONE_MINUTE => /* do something else */, _ => /* do something else again */, } } }

Now this code is, in all probability, buggy. Probably I meant to use the notion of equality

同一性、等式
that Duration defined,
定義する
where seconds and minutes are normalized. But that is not the behavior
ふるまい
I will see -- instead I will use a pure structural match.
一致する、マッチさせる
What's worse, this means the code will probably work in my local tests, since I like to say "one minute", but it will break when I demo it for my customer, since she prefers to write "60 seconds".

Example: Floating point numbers. Another example is floating point numbers. Consider

考える、みなす
the case of 0.0 and -0.0: these two values are distinct,
区別された/独立した
but they typically
一般的に、典型的に
behave
振る舞う
the same; so much so that they compare equal
等しい
(that is, 0.0 == -0.0 is true). So it is likely that code such as:

#![allow(unused)] fn main() { match some_computation() { 0.0 => ..., x => ..., } }

did not intend to discriminate between zero and negative

負の
zero. In fact, in the compiler today, match
一致する、マッチさせる
will compare 0.0 and -0.0 as equal.
等しい
We simply do not extend
拡張する
that courtesy to user-defined types.

Example: observing private fields. The current constant

定数
expansion code does not consider
考える、みなす
privacy. In other words, constants
定数
are expanded into equivalent
等価
patterns, but those patterns may not have been something the user could have typed because of privacy rules. Consider
考える、みなす
a module like:

#![allow(unused)] fn main() { mod foo { pub struct Foo { b: bool } pub const V1: Foo = Foo { b: true }; pub const V2: Foo = Foo { b: false }; } }

Note that there is an abstraction boundary here: b is a private field. But now if I wrote code from another module that matches

一致する、マッチさせる
on a value of type Foo, that abstraction boundary is pierced:

#![allow(unused)] fn main() { fn bar(f: x::Foo) { // rustc knows this is exhaustive because if expanded `V1` into // equivalent patterns; patterns you could not write by hand! match f { x::V1 => { /* moreover, now we know that f.b is true */ } x::V2 => { /* and here we know it is false */ } } } }

Note that, because Foo does not implement

実装する
PartialEq, just having access to V1 would not otherwise
さもなければ
allow
許可する、可能にする
us to observe the value of f.b. (And even if Foo did implement
実装する
PartialEq, that implementation
実装
might not read f.b, so we still would not be able to observe its value.)

More examples. There are numerous possible examples here. For example, strings that compare using case-insensitive comparisons,

比較
but retain the original case for reference,
参照
such as those used in file-systems. Views that extract
抽出する
a subportion of a larger value (and hence which should only compare that subportion). And so forth.

Disadvantage: Scaling
規模を変更する
to associated constants
定数
and generic integers
整数

Rewriting constants

定数
into patterns requires that we can fully
完全に
evaluate
the constant
定数
at the time of exhaustiveness checking. For associated constants
定数
and type-level integers,
整数
that is not possible -- we have to wait until monomorphization time. Consider:
考える、みなす

#![allow(unused)] fn main() { trait SomeTrait { const A: bool; const B: bool; } fn foo<T:SomeTrait>(x: bool) { match x { T::A => println!("A"), T::B => println!("B"), } } impl SomeTrait for i32 { const A: bool = true; const B: bool = true; } impl SomeTrait for u32 { const A: bool = true; const B: bool = false; } }

Is this match

一致する、マッチさせる
exhaustive? Does it contain
含む
dead code? The answer will depend on whether T=i32 or T=u32, of course.

Advantages of the current approach

However, structural equality

同一性、等式
also has a number of advantages:

Better optimization. One of the biggest "pros" is that it can potentially enable nice optimization. For example, given

与えられた
constants
定数
like the following:

#![allow(unused)] fn main() { struct Value { x: u32 } const V1: Value = Value { x: 0 }; const V2: Value = Value { x: 1 }; const V3: Value = Value { x: 2 }; const V4: Value = Value { x: 3 }; const V5: Value = Value { x: 4 }; }

and a match

一致する、マッチさせる
pattern like the following:

#![allow(unused)] fn main() { match v { V1 => ..., ..., V5 => ..., } }

then, because pattern matching

一致する、マッチさせる
is always a process of structurally extracting
抽出する
values, we can compile this to code that reads the field x (which is a u32) and does an appropriate switch on that value. Semantic equality
同一性、等式
would potentially force a more conservative compilation strategy.

Better exhautiveness and dead-code checking. Similarly,

同様に
we can do more thorough exhaustiveness and dead-code checking. So for example if I have a struct
構造、構造体
like:

#![allow(unused)] fn main() { struct Value { field: bool } const TRUE: Value { field: true }; const FALSE: Value { field: false }; }

and a match

一致する、マッチさせる
pattern like:

#![allow(unused)] fn main() { match v { TRUE => .., FALSE => .. } }

then we can prove that this match

一致する、マッチさせる
is exhaustive. Similarly,
同様に
we can prove that the following match
一致する、マッチさせる
contains
含む
dead-code:

#![allow(unused)] fn main() { const A: Value { field: true }; match v { TRUE => ..., A => ..., } }

Again, some of the alternatives might not allow

許可する、可能にする
this. (But note the cons, which also raise the question of exhaustiveness checking.)

Nullary variants and constants

定数
are (more) equivalent.
等価
Currently, there is a sort of equivalence between enum variants and constants,
定数
at least with respect to pattern matching.
一致する、マッチさせる
Consider
考える、みなす
a C-like enum:

#![allow(unused)] fn main() { enum Modes { Happy = 22, Shiny = 44, People = 66, Holding = 88, Hands = 110, } const C: Modes = Modes::Happy; }

Now if I match

一致する、マッチさせる
against Modes::Happy, that is matching
一致する、マッチさせる
against an enum variant, and under all the proposals I will discuss below, it will check the actual
実際の
variant of the value being matched
一致する、マッチさせる
(regardless of whether Modes implements
実装する
PartialEq, which it does not here). On the other hand, if matching
一致する、マッチさせる
against C were to require a PartialEq impl, then it would be illegal.
文法違反
Therefore matching
一致する、マッチさせる
against an enum variant is distinct
区別された/独立した
from matching
一致する、マッチさせる
against a constant
定数
.

Detailed design
設計(する)

The goal of this RFC is not to decide between semantic and structural equality.

同一性、等式
Rather, the goal is to restrict
制限する
pattern matching
一致する、マッチさせる
to that subset
部分集合
of types where the two variants behave
振る舞う
roughly the same.

The structural match
一致する、マッチさせる
attribute

We will introduce

導入する
an attribute #[structural_match] which can be applied
適用する
to struct
構造、構造体
and enum types. Explicit
明示的な
use of this attribute will (naturally) be feature-gated. When converting
変換する
a constant
定数
value into a pattern, if the constant
定数
is of struct
構造、構造体
or enum type, we will check whether this attribute is present
ある
on the struct
構造、構造体
-- if so, we will convert
変換する
the value as we do today. If not, we will report an error that the struct/enum value cannot be used in a pattern.

Behavior
ふるまい
of #[derive(Eq)]

When deriving the Eq trait, we will add the #[structural_match] to the type in question. Attributes added

たす
in this way will be exempt from the feature gate.

Exhaustiveness and dead-code checking

We will treat

取り扱う
user-defined structs
構造、構造体
"opaquely" for the purpose of exhaustiveness and dead-code checking. This is required to allow
許可する、可能にする
for semantic equality
同一性、等式
semantics in the future, since in that case we cannot rely on Eq to be correctly implemented
実装する
(e.g., it could always return false, no matter values are supplied to it, even though it's not supposed to). The impact of this change has not been evaluated
評価する(される)
but is expected to be very small, since in practice it is rather challenging to successfully make an exhaustive match
一致する、マッチさせる
using user-defined constants,
定数
unless they are something trivial like newtype'd booleans
真偽の
(and, in that case, you can update the code to use a more extended
拡張された
pattern).

Similarly,

同様に
dead code detection should treat
取り扱う
constants
定数
in a conservative fashion. that is, we can recognize that if there are two arms using the same constant,
定数
the second one is dead code, even though it may be that neither will matches
一致する、マッチさせる
(e.g., match
一致する、マッチさせる
foo { C => _, C => _ }
). We will make no assumptions about two distinct
区別された/独立した
constants,
定数
even if we can concretely evaluate them to the same value.

One unresolved question (described below) is what behavior

ふるまい
to adopt for constants
定数
that involve no user-defined types. There, the definition
定義
of Eq is purely under our control,
制御する
and we know that it matches
一致する、マッチさせる
structural equality,
同一性、等式
so we can retain our current aggressive analysis
解析
if desired.

Phasing
段階

We will not make this change instantaneously. Rather, for at least one release cycle, users who are pattern matching

一致する、マッチさせる
on struct
構造、構造体
types that lack #[structural_match] will be warned about imminent breakage.

Drawbacks

This is a breaking change, which means some people might have to change their code. However, that is considered

みなす、考慮する
extremely unlikely, because such users would have to be pattern matching
一致する、マッチさせる
on constants
定数
that are not comparable
比較可能
for equality
同一性、等式
(this is likely a bug in any case).

Alternatives

Limit matching

一致する、マッチさせる
to builtin types. An earlier version of this RFC limited matching
一致する、マッチさせる
to builtin types like integers
整数
(and tuples
タプル(複合型)
of integers). This RFC is a generalization of that which also accommodates struct
構造、構造体
types that derive
派生する
Eq.

Embrace current semantics (structural equality). Naturally we could opt to keep the semantics as they are. The advantages and disadvantages are discussed above.

Embrace semantic equality.

同一性、等式
We could opt to just go straight towards "semantic equality".
同一性、等式
However, it seems better to reset the semantics to a base
基となる、基底(の)
point that everyone can agree on, and then extend
拡張する
from that base
基となる、基底(の)
point. Moreover,
その上
adopting semantic equality
同一性、等式
straight out would be a riskier breaking change, as it could silently change the semantics of existing programs (whereas the current proposal only causes compilation to fail, never changes what an existing program will do).

Discussion thread summary

This section

summarizes various
さまざまな
points that were raised in the internals thread which are related to patterns but didn't seem to fit elsewhere.

Overloaded patterns. Some languages,

言語
notably Scala, permit
許す
overloading of patterns. This is related to "semantic equality"
同一性、等式
in that it involves executing
実行(する)
custom, user-provided code at compilation time.

Pattern synonyms. Haskell offers a feature called

呼び出し
"pattern synonyms" and it was argued that the current treatment of patterns can be viewed as a similar
似ている、同様の
feature. This may be true, but constants-in-patterns are lacking a number of important features from pattern synonyms, such as bindings, as discussed in this response. The author feels that pattern synonyms might be a useful feature, but it would be better to design
設計(する)
them as a first-class feature, not adapt constants
定数
for that purpose.

Unresolved questions

What about exhaustiveness etc on builtin types? Even if we ignore

無視する
user-defined types, there are complications around exhaustiveness checking for constants
定数
of any kind related to associated constants
定数
and other possible future extensions. For example, the following code fails to compile because it contains
含む
dead-code:

#![allow(unused)] fn main() { const X: u64 = 0; const Y: u64 = 0; fn bar(foo: u64) { match foo { X => { } Y => { } _ => { } } } }

However, we would be unable to perform such an analysis

解析
in a more generic context,
文脈、背景
such as with an associated constant:
定数

#![allow(unused)] fn main() { trait Trait { const X: u64; const Y: u64; } fn bar<T:Trait>(foo: u64) { match foo { T::X => { } T::Y => { } _ => { } } } }

Here, although it may well be that T::X == T::Y, we can't know for sure. So, for consistency, we may wish to treat

取り扱う
all constants
定数
opaquely regardless
〜に関わらず
of whether we are in a generic context
文脈、背景
or not. (However, it also seems reasonable to make a "best effort" attempt at exhaustiveness and dead pattern checking, erring on the conservative side in those cases where constants
定数
cannot be fully
完全に
evaluated.)

A different argument

引数
in favor of treating
取り扱う
all constants
定数
opaquely is that the current behavior
ふるまい
can leak details that perhaps were intended to be hidden. For example, imagine that I define
定義する
a fn hash that, given
与えられた
a previous
前の
hash and a value, produces
産出、産出する
a new hash. Because I am lazy and prototyping my system, I decide for now to just ignore
無視する
the new value and pass the old hash through:

#![allow(unused)] fn main() { const fn add_to_hash(prev_hash: u64, _value: u64) -> u64 { prev_hash } }

Now I have some consumers of my library and they define

定義する
a few constants:
定数

#![allow(unused)] fn main() { const HASH_OF_ZERO: add_to_hash(0, 0); const HASH_OF_ONE: add_to_hash(0, 1); }

And at some point they write a match

一致する、マッチさせる
statement:

#![allow(unused)] fn main() { fn process_hash(h: u64) { match h { HASH_OF_ZERO => /* do something */, HASH_OF_ONE => /* do something else */, _ => /* do something else again */, } }

As before, what you get when you compile this is a dead-code error, because the compiler can see that HASH_OF_ZERO and HASH_OF_ONE are the same value.

Part of the solution here might be making "unreachable patterns" a warning and not an error. The author feels this would be a good idea regardless

〜に関わらず
(though not necessarily as part of this RFC). However, that's not a complete
完全な
solution, since -- at least for bool constants
定数
-- the same issues arise if you consider
考える、みなす
exhaustiveness checking.

On the other hand, it feels very silly for the compiler not to understand that match

一致する、マッチさせる
some_bool { true => ..., false => ... } is exhaustive. Furthermore, there are other ways for the values of constants
定数
to "leak out", such as when part of a type like [u8; SOME_CONSTANT] (a point made by both arielb1 and glaebhoerl on the internals thread). Therefore, the proper way to address this question is perhaps to consider
考える、みなす
an explicit
明示的な
form
形式、形態、形作る
of "abstract constant".
定数