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.
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.
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" ),
}
}
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:
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).
Scaling規模を変更する
to associated constants.定数
The current approach does not permit許す
associated constants定数
or generic integers整数
to be used in a match一致する、マッチさせる
statement.文
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 => ,
ONE_MINUTE => ,
_ => ,
}
}
}
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) {
match f {
x::V1 => { }
x::V2 => { }
}
}
}
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.
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.
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定数
.
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.
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.
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 .
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.
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.
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).
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).
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.
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 => ,
HASH_OF_ONE => ,
_ => ,
}
}
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".定数