新しいトレイト実装による破壊的変更
標準ライブラリへの多くの PR では、すでに安定化されているトレイトに対して新しい impl が追加されています。 これは、利用側を実にさまざまな奇妙な形で壊す可能性があります。 以下は、標準ライブラリに加えられた変更だけからは明らかでない可能性がある、 新しいトレイト実装による破壊的変更の例です。
2つ目のジェネリック impl が導入されると推論が壊れる
Rust は推論時に、ジェネリックトレイトに対する impl が1つしかないという事実を利用します。 2つ目の impl によってそのジェネリックの型が曖昧になると、これは壊れます。 たとえば、次があるとします。
// `std` 内
impl From<&str> for Arc<str> { .. }
// 外部の `lib` 内
let b = Arc::from("a");
その後、次を追加します。
impl From<&str> for Arc<str> { .. }
+ impl From<&str> for Arc<String> { .. }
すると、
let b = Arc::from("a");
はもはやコンパイルされません。これは、以前は Box<T> の T を推論で特定することに依存していたためです。
この種の破壊的変更は許容される場合もありますが、crater 実行によって影響範囲を見積もるべきです。
新しい impl が導入されると Deref 型強制が壊れる
Rust は、引数が直接型チェックを通らない場合、有効なトレイト impl を見つけるために Deref 型強制を使用します。 これは impl が1つしかない場合にのみ発生するように見えるため、新しい impl を導入すると、Deref 型強制に依存している利用側が壊れる可能性があります。 たとえば、次があるとします。
// `std` 内
impl Add<&str> for String { .. }
impl Deref for String { type Target = str; .. }
// 外部の `lib` 内
let a = String::from("a");
let b = String::from("b");
let c = a + &b;
その後、次を追加します。
impl Add<&str> for String { .. }
+ impl Add<char> for String { .. }
すると、
let c = a + &b;
はもはやコンパイルされません。これは、&String を &str へ強制するために Deref を使用しようとしなくなるためです。
この種の破壊的変更は許容される場合もありますが、crater 実行によって影響範囲を見積もるべきです。
#[fundamental] 型
#[fundamental] 属性で注釈された型には、異なるコヒーレンス規則があります。
詳細は RFC 1023 を参照してください。
これには次が含まれます。
&T&mut TBox<T>Pin<T>
通常、新しいトレイト実装における破壊的変更の範囲は、推論と Deref 型強制に限定されます。
#[fundamental] 型に対する新しいトレイト実装は、下流の impl と重複し、他の種類の破壊的変更を引き起こす可能性があります。