Rust (Cargo)におけるクレートのバージョンと互換性について

Cargoのクレートのバージョンは**[Semantic Versioning]**（**SemVer**）によって互換性が担保されている[^1]ので、ライブラリの利用者は`cargo update`を実行するだけで互換性を保ったまま依存ライブラリをアップデートできます。

[Semantic Versioning]: http://semver.org/
[^1]: [The Manifest Format](http://doc.crates.io/manifest.html#the-version-field) (doc.crates.io)

しかし、それはあくまでライブラリとアプリケーションの双方でバージョンの設定が適切に行われた場合の話であり、これが不十分であった場合は`cargo update`を実行した途端にコンパイルが通らなくなってしまうといったことが起こりえます。

この記事では適切なバージョニングのための基本的な考え方を掴むことを目標とします。記事の前半ではCargoにおける依存クレートのバージョンの扱いについて述べ、それを踏まえて後半ではライブラリ作者向けにバージョニングに関するルールや慣習について述べていきます。

## Semantic Versioning

Cargo固有の問題に進む前に、一般的なSemVerのルールについて簡単にまとめておきます。

SemVerは基本的に`X.Y.Z`のような形で表され、ここで`X`を**メジャーバージョン**（**major version**）、`Y`を**マイナーバージョン**（**minor version**）、`Z`を**パッチバージョン**（**patch version**）と呼びます[^2]。

[^2]: 他にも`1.0.0-alpha.1`のようにpre-release versionと呼ばれる情報を指定することもできるが、この記事では扱わない。

SemVerの変更は以下のように行います。

- メジャーバージョンは後方互換性のないAPIの変更を行ったときに上げる
- マイナーバージョンは後方互換な機能追加を行ったときに上げる
- パッチバージョンは後方互換なバグ修正を行ったときに上げる

ただし、メジャーバージョンが`0`の場合（`0.x.y`）のバージョンの上げ方はSemVerの仕様上規定されていません。例えば、`0.1.0`と`0.1.1`の間に全く互換性がないといったこともあり得るのです。しかし、メジャーバージョンが`0`であるクレートがたくさんある中でこのルールに厳密に従うのは現実的ではないので、Cargoではより互換性に厳格な慣習（次節で述べる）が採用されています。

## `Cargo.toml`の`[dependencies]`セクションにおけるバージョンの解釈[^3]

[^3]: 参考: [Specifying Dependencies](http://doc.crates.io/specifying-dependencies.html) (doc.crates.io)、[semver | npm Documentation](https://docs.npmjs.com/misc/semver) (docs.npmjs.com)（`semver`クレートはnpmの表記を採用している）

ご存知の通り、`Cargo.toml`では依存するクレートを以下のように指定します。

```toml
[dependencies]
time = "0.1.12"
```

ここで、文字列`0.1.12`は`^0.1.12`の省略形であり、`>=0.1.12`かつ`<0.2`の範囲内のバージョンを表します。これらのバージョン文字列（requirement）の解釈は[`semver`]クレートによって実装されており、具体的には以下のルールに従って行われます。

[`semver`]: https://github.com/steveklabnik/semver

### Caret requirement

**Caret requirement**は、クレートのあるバージョンに対してSemVer的に互換（SemVer compatible）なバージョンの範囲を指定します。具体的には、`X.Y.Z`の３つの番号のうち、最も左の`0`でない番号（ただし全て`0`のときは最も右側の`0`）を保つようなアップデートのみを許容します。

```text
^1.2.3 := >=1.2.3 <2.0.0
^1.2   := >=1.2.0 <2.0.0 (^1.2.0も同じ範囲を指す)
^1     := >=1.0.0 <2.0.0 (^1.0や^1.0.0も同じ範囲を指す)
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4 (=0.0.3と同じ)
^0.0   := >=0.0.0 <0.1.0
^0     := >=0.0.0 <1.0.0 (^0と^0.0がそれぞれ別の範囲を指すことに注意)
```

この表記は、（一般のSemVerと違って）メジャーバージョンが`0`であるクレートについても一定の互換性を保証しています。

Caret requirementは`Cargo.coml`の`dependencies`のバージョン指定におけるデフォルトの表記です。つまり、単に`x.y.z`、`x.y`、`x`と書いた場合はそれぞれ自動的に`^x.y.z`、`^x.y`、`^x`が指定されたもの同様に解釈されます。実際、crates.io上のクレートの多くはdependenciesでこの省略表記のみを使用しているので、特に事情がない限りはこの表記を使っておけば問題はないでしょう。

### Tilde requirements

Caret requirementの他に`~1.2.3`のような**Tilde requirement**と呼ばれる形式もあります。これは、あるバージョンの次のマイナー／メジャーアップデートまでのバージョンを許容します。つまり、マイナーバージョンが指定されていれば（`~x.y.z`や`~x.y`）パッチバージョンの変更のみを可能とし、そうでなければ（`~x`）マイナーバージョンの変更を可能とします。

```text
~1.2.3 := >=1.2.3 <1.3.0
~1.2   := >=1.2.0 <1.3.0 (~1.2.0と同じ)
~1     := >=1.0.0 <2.0.0 (1.*と同じ)
~0.2.3 := >=0.2.3 <0.3.0
~0.2   := >=0.2.0 <0.3.0 (~0.2.0と同じ)
~0     := >=0.0.0 <1.0.0 (0.*と同じ)
```

### Wildcard requirements

**Wildcard requirement**はその名の通り`0.0.*`のようにワイルドカードで範囲を指定します。なお、`*`のようにワイルドカードのみによるrequirementも可能ではありますが、そのようなrequirementを含むクレートは互換性を損ねる恐れがあるためcrates.ioでは拒否されます[^4]。

[^4]: [Frequently Asked Questions](http://doc.crates.io/faq.html#can-libraries-use--as-a-version-for-their-dependencies) (doc.crates.io)

```text
* := >=0.0.0 (全てのバージョン。crates.ioでは使えない)
1.* := >=1.0.0 <2.0.0 (^1と同じ)
0.0.* := >=0.0.0 <0.1.0
```

### Inequality requirements

**Inequality requirement**はその名の通り、不等号で範囲を直接示す形式です。

```text
>= 1.2.0 (2.Y.Z, 3.Y.Z, 4.Y.Z, ...なども含んでしまうため推奨しない)
> 1 (上と同様)
< 2
= 1.2.3
```

### Multiple requirements

複数のrequirementをカンマ区切りで指定することが出来ます（例：`>= 1.2, < 1.5`）。

## ライブラリのバージョニング

### 後方非互換な変更

記事冒頭ではSemVer一般のルールについて述べましたが、実際にRustのライブラリを作成していると、どのタイミングでメジャー・マイナー・パッチのバージョンを上げるべきか判別しづらいケースがあります。

SemVerの仕様上、後方互換性のない変更の際にはメジャーバージョンを上げなくてはならないとされています。しかし、Rustではほぼ全てのAPIの変更が厳密には後方互換ではないのです。その例として以下のアプリケーションを考えます。

```rust
extern crate lib;

use lib::*;

mod module {
    #[derive(Debug)]
    pub struct Bar(pub i32);
}

use module::*;

fn main() {
    let foo = Foo; // `lib`からインポートした型
    let bar = Bar(1);
    println!("I have {:?} and {:?}", foo, bar);
}
```

上のコードではモジュール`module`と外部のライブラリ`lib`から`*`（glob）を用いてアイテムを無差別にインポートしています。ここでもし`lib`がアップデートして`Bar`という型をトップレベルに追加すると問題が起こります。試しにコンパイルしようとすると以下のようにエラーとなります[^5]。

[^5]: 環境は`rustc 1.22.1 (05e2e1c41 2017-11-22)`のstable。

```text
error: `Bar` is ambiguous
  --> src/main.rs:14:15
   |
14 |     let bar = Bar(1);
   |               ^^^
   |
note: `Bar` could refer to the name imported here
  --> src/main.rs:3:5
   |
3  | use lib::*;
   |     ^^^^^^^
note: `Bar` could also refer to the name imported here
  --> src/main.rs:10:5
   |
10 | use module::*;
   |     ^^^^^^^^^^
   = note: consider adding an explicit import of `Bar` to disambiguate
```

このように、ライブラリにパブリックなアイテムを追加するだけでも後方互換性が崩れてしまうのです。

### RFC 1105

上の例から、些細な変更が後方互換性を破壊しうることが分かりましたが、かといってこういった変更を行う度にSemVerの仕様に従ってメジャーバージョンを上げるのは非現実的です。そこで、そのような変更をマイナーバージョンの変更だけでできるようにするために、[RFC 1105]（以降、単にRFCと呼ぶ）がSemVerの規定に対していくつかの例外を定めています。

[RFC 1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md

RFCでは以下の用語が定義されています。ただし訳語は私が勝手につけたものです。

- **メジャーチェンジ**（**major change**）: メジャーバージョン（`0`の場合はマイナーバージョン）の変更を必要とする変更
- **マイナーチェンジ**（**minor change**）: マイナーバージョンの変更を必要とする変更
- **破壊的変更**（**breaking change**）: 当該ライブラリに依存するアプリケーション（downstream code）のコンパイルを通らなくしてしまう可能性のある変更

SemVerの仕様に厳密に従えば破壊的変更は全てメジャーチェンジになりますが、RFCではいくつかの破壊的変更を例外的にマイナーチェンジと定めます。

RFCの方針として、マイナーチェンジの引き起こす非互換性は最悪でもアプリケーション側にわずかな変更しか要求しないべきとされています。例えば、アプリケーション側への型注釈や[UFCS]の追加だけでコンパイルが通せるような破壊的変更はマイナーチェンジにできます。前節の例で言えば、アプリケーション側のglobを開いて`use lib::Foo; use module::Bar;`と書けば互換性を守れるのでこれもマイナーチェンジと定めることが可能です。

[UFCS]: https://doc.rust-lang.org/book/ufcs.html

## <span id="list-of-breaking-changes-from-rfc-1105"></span>RFCにおける破壊的変更の例の一覧

RFCはいくつかの破壊的変更の例を挙げて、それぞれメジャーチェンジとマイナーチェンジのどちらであるか示しています。しかし、それらを一つひとつ紹介すると長くなるので、下表に各項目の抜粋だけ載せておきます。

なお、ここで明示されていない全ての破壊的変更はメジャーチェンジとして解釈するものと規定されています。

変更のスコープ | 変更の内容 | 種別(Minorは太字) | 備考
---|---|---|---
[Crates](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#crates) | [Going from stable to nightly](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-going-from-stable-to-nightly) | Major | 
 | [Altering the use of Cargo features](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-altering-the-use-of-cargo-features) | **Minor** | 
[Modules](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#modules) | [Renaming/moving/removing any public items](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-renamingmovingremoving-any-public-items) | Major | 
 | [Adding new public items](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-new-public-items) | **Minor** | 関連：[RFC 1560](https://github.com/rust-lang/rfcs/blob/master/text/1560-name-resolution.md#explicit-names-may-shadow-implicit-names)（globによってインポートされたアイテムの上書き）（[Rust 1.15.0](https://github.com/rust-lang/rust/blob/1.15.0/RELEASES.md#version-1150-2017-02-02)にてstabilizeされた）<br>[Issue rust-lang/rfcs#1311](https://github.com/rust-lang/rfcs/issues/1311)（名前空間を汚染しないトレイトのインポート）
[Structs](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#structs) | [Adding a private field when all current fields are public](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-adding-a-private-field-when-all-current-fields-are-public) | Major | 
 | [Adding a public field when no private field exists](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-adding-a-public-field-when-no-private-field-exists) | Major | 
 | [Adding or removing private fields when at least one already exists (before and after the change)](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-or-removing-private-fields-when-at-least-one-already-exists-before-and-after-the-change) | **Minor** | ただし後述の[auto traits](#sendやsync-auto-traitsの実装の削除)に注意
 | [Going from a tuple struct with all private fields (with at least one field) to a normal struct, or vice versa](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-going-from-a-tuple-struct-with-all-private-fields-with-at-least-one-field-to-a-normal-struct-or-vice-versa) | **Minor** | 
[Enums](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#enums) | [Adding new variants](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-adding-new-variants) | Major | 関連：[RFC 2008 non_exhaustive](https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md)（破壊的変更なしでenumにヴァリアントを追加できるようにする仕組み）
 | [Adding new fields to a variant](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-adding-new-fields-to-a-variant) | Major | 
[Traits](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#traits) | [Adding a non-defaulted item](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-adding-a-non-defaulted-item) | Major | [Rust API guidelines][rust-lang-nursery/api-guidelines]のC-SEALEDによって互換性の改善が見込める
 | [Any non-trivial change to item signatures](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-any-non-trivial-change-to-item-signatures) | Major | 関連：[issue rust-lang/rust#27336](https://github.com/rust-lang/rust/issues/27336)（RFC 0213 defaulted-type-params: fn itemの型パラメータへのデフォルト値の導入）
 | [Adding a defaulted item](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-a-defaulted-item) | **Minor** (条件付き） | 
 | [Adding a defaulted type parameter](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-a-defaulted-type-parameter) | **Minor** | 
[Trait implementations](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#trait-implementations) | [Implementing any "fundamental" trait](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-implementing-any-fundamental-trait) | Major | `Fn*`や`Sized`などがfundamenal traitに当たる
 | [Implementing any non-fundamental trait](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-implementing-any-non-fundamental-trait) | **Minor** | 
[Inherent implementations](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#inherent-implementations) | [Adding any inherent items](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-any-inherent-items) | **Minor** | 
[Signatures in type definitions](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#signatures-in-type-definitions) | [Tightening bounds](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-tightening-bounds) | Major | 
 | [Loosening bounds](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-loosening-bounds) | **Minor** | 
 | [Adding defaulted type parameters](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-adding-defaulted-type-parameters) | **Minor** | 
 | [Generalizing to generics](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-generalizing-to-generics) | **Minor** | 
[Signatures in functions](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#signatures-in-functions) | [Adding/removing arguments](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#major-change-addingremoving-arguments) | Major | 
 | [Introducing a new type parameter](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-introducing-a-new-type-parameter) | **Minor** | 
[Lints](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#lints) | [Introducing new lint warnings/errors](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-introducing-new-lint-warningserrors) | **Minor** | 

## RFCに示されていない破壊的変更

以下では、RFCに示されていない破壊的変更のうち私が特筆すべきと感じたものを列挙します。

### 新しいバージョンの言語／標準ライブラリ機能の使用

例えば`try!`マクロで書かれていたコードを`?`演算子で書き換えたとすると、そのコードはコンパイルにRust 1.13以降を必要とするようになります。これはバージョン1.13未満のユーザにとっては破壊的変更とも言えそうです。

しかし、実際にこれを破壊的変更とするかどうかについてはコミュニティ内でもまだ明確な合意が形成されていないように見えます。Rust library teamのAlex Crichton氏はGitHubの[rust-lang-nursery] organizationのクレートについてこのような変更をマイナーチェンジと定める[RFC]を提案していますが、互換性について懸念する意見が多かったため判断が保留されています。実際、`bitflags`クレートや`rand`クレートなどは現在も`?`演算子でなく`try!`マクロを使うなど、古いバージョンのRustとの互換性を保っています。

[rust-lang-nursery]: https://github.com/rust-lang-nursery
[RFC]: https://github.com/rust-lang/rfcs/pull/1619
[PR1709]: https://github.com/rust-lang/rfcs/pull/1709

### `Cargo.toml`の`dependencies`セクションの変更

ライブラリが依存しているクレートのバージョンの変更は、次の条件を満たすとき破壊的変更になりえます。

- 依存先クレートのアイテムを自ライブラリのパブリックなAPIに露出している

これは依存先クレート（`lib`とする）のアイテムを、`pub use lib::foo;`や`pub fn bar(bar: lib::Baz)`のようにクレート外部から見えるようにしているようなケースです。このような依存のことをpublic dependencyと呼びます。

例えば`lib = "=0.1"`のトレイト`lib::Trait`と`lib = "0.2"`の`lib::Trait`は、たとえその内容に変更がないとしても全く別のトレイトとして扱われるため、一方のバージョンのトレイトに対する実装と他方のバージョンに対する実装の間に互換性がありません。そのため、`lib`のバージョンを変更することで同じく`lib::Trait`に依存するアプリケーションとの互換性が損なわれてしまうのです。

ただし依存先クレートが後述の[semver trick](#the-semver-trick)を用いている場合は破壊的変更にならない場合があります。

また、[RFC 1977]がpublic dependenciesを適切に管理するための仕組みを定めています（1.22.1 stable時点で未実装）。

[RFC 1977]: https://github.com/rust-lang/rfcs/blob/master/text/1977-public-private-dependencies.md

- 依存先クレートの`Cargo.toml`で`links`キーが指定されている（主にFFI関連クレート）

Cのライブラリへバインディングするときにそのライブラリの複数のバージョンへ同時にリンクしようとすると、Cには名前マングリングの仕組みがないためリンク時や実行時に問題が起こりえます[^6]。`links`キーはそのようなエラーをCargoの時点で補足するための仕組みです。具体的には、Cargoは`Cargo.toml`の`[package]`セクションで同一の`links`キーが指定されているクレート同士（同一クレートの別バージョンも含む）が同じバイナリに混在するようなビルドをエラーとして扱います。

[^6]: 参考：[Alex Crichton氏のコメント](https://github.com/rust-lang/rfcs/issues/1386#issuecomment-161058806)

例えば、`links`キーを持つ`foo-sys`クレートがあったとして、自ライブラリとそれに依存するアプリケーションがともに`foo-sys = "0.1"`に（間接的／直接的問わず）依存しているとします。ここで自ライブラリの依存を`foo-sys = "0.2"`に切り替えると、アプリケーションは`foo-sys`の`^0.1`と`^0.2`に同時にリンクしようとしてエラーとなってしまうので破壊的変更になります。

一見して、自ライブラリの依存を`foo-sys = ">=0.1, <0.3"`のようにすれば問題がなくなるように見えるかもしれません。しかし、このように指定しても`cargo update`を実行するとCargoが`>=0.1, <0.3`の範囲のうち最大のバージョンを取りに行ってしまうので結局`^0.1`系と`^0.2`系が混在することとなってしまいエラーになります。

原則[^7]として`links`キーはFFIのための仕組みなので、FFI関連のクレート（典型的には`*-sys`という名前のクレートやそのラッパ）がこれに関係してきます。また、[`ring`]クレートもCやアセンブリのコードを含むため`links`キーを持ちます（なのでこれに依存している蓋然性が高い暗号系のクレート全般は要注意）。

[^7]: 原則というからには例外もあって、pure Rustの[`rayon-core`](https://github.com/rayon-rs/rayon/tree/master/rayon-core)クレートが`links`キーを持つ。ただしこのクレートはメジャーチェンジを行わないことを明言しているため、利用者として互換性を気にする必要は特にない。
[`ring`]: https://github.com/briansmith/ring

- 依存先クレートのサポートするRustのバージョンが引き上げられる

これは前節の内容とも繋がる話ですね。ただ、あるクレートの要求するRustのバージョンを調べることは現時点であまり容易な作業ではないので、ここまで気にし始めると正直キリがないような気がします（私のライブラリの更新時はここまで調べていません）。

### パブリックなアイテムをオプショナルにする

あるアイテムに`#[cfg(feature = "...")]`をつけてオプショナルにするのはアイテムの削除と同様の働きをするため破壊的変更になります。これは当該のfeatureが`Cargo.toml`で`default` featureとして指定されている場合も同様です（アプリケーション側で`default-feature = false`が指定されている場合に無効になるため）。

これに関しては[Feature Request: Ability to disable single default features · Issue #3126 · rust-lang/cargo](https://github.com/rust-lang/cargo/issues/3126)の提案が受け入れられれば互換性が改善される可能性がありますが、いずれにしても破壊的変更であることに違いはないです。

### featureの削除

前節とは逆にオプショナルだった機能を常に有効化して（これ単独ではメジャーチェンジでない）、`Cargo.toml`から当該featureを削除することも破壊的変更になります。というのも、Cargoは存在しないfeatureへの依存をエラーとして扱うので、featureを削除した時点でアプリケーション側にエラーが発生してしまうからです。

### C言語ライクな列挙型の判別子の順序の変更

[C言語ライクな列挙型]とは、以下のようにヴァリアントがデータを一切持たない列挙型のことを言います。

[C言語ライクな列挙型]: http://rust-lang-ja.org/rust-by-example/custom_types/enum/c_like.html

```rust
enum CLike {
    A,
    B,
    C,
}
```

このような列挙型の値は、Safe Rustの範囲で整数にキャストすることができます。

```rust
fn main() {
    assert_eq!(0, CLike::A as u8);
    assert_eq!(1, CLike::B as u8);
    assert_eq!(2, CLike::C as u8);
}
```

この値は宣言時の判別子の順序に依存するため、これを並び替えることは破壊的変更になります。

ただし、この値は宣言時に明示的に指定することも出来るので、値を適切に設定すれば破壊的変更なしで並び替えることができます。

```rust
enum CLike {
    C = 2,
    B = 1,
    A = 0,
}

fn main() {
    assert_eq!(0, CLike::A as u8);
    assert_eq!(1, CLike::B as u8);
    assert_eq!(2, CLike::C as u8);
}
```

### `Send`や`Sync` (auto traits)の実装の削除

[`Send`]と[`Sync`]（と[`UnwindSafe`]と[`RefUnwindSafe`]）は**auto trait** [^8]と呼ばれる特殊な種類のトレイトです。Auto traitであるようなトレイト`Trait`は以下の性質を持ちます。

[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
[`UnwindSafe`]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html
[`RefUnwindSafe`]: https://doc.rust-lang.org/std/panic/trait.RefUnwindSafe.html
[^8]: 古いドキュメントなどではopt-in builtin trait (OIBIT)と呼ばれていることが多い。

1. ある構造体や列挙型の全てのメンバ（ただし幽霊型`PhantomData<T>`の場合は`T`）が`Trait`を実装している（あるいはメンバを1つも含まない）ならばその型は自動的に`Trait`を実装する
2. 1.を満たさない型でも、明示的に`impl Trait`を書くことで実装を上書きできる（`Send`と`Sync`の場合は`unsafe impl`）
3. 1.を満たす型でも、明示的に`impl !Trait`を書くことで実装しないようにできる（ただし執筆時時点でunstable[^9]）

[^9]: `#![feature(optin_builtin_traits)]`が必要（`rustc 1.24.0-nightly (dc39c3169 2017-12-17)`時点）。ただし、フィールドの追加を許容できるなら`Trait`を実装しない幽霊型`PhantomData<T>`（例えば`Send`や`Sync`の場合は`PhantomData<*const ()>`など）で同様のことが実現できる。

1.の性質から、あるauto traitを自動的に実装している型にそのトレイトを実装しないメンバ（public/private問わず）を追加することは、トレイトの実装の削除と同じ働きをするのでメジャーチェンジになります。

`Send`や`Sync`を実装しないプリミティブ型と標準ライブラリの型の例（全てではない）として以下のものがあります。

- [`*const T`][pointer] (`!Send + !Sync`)
- `*mut T` (`!Send + !Sync`)
- [`std::rc::Rc<T>`][Rc] (`!Send + !Sync`)
- [`std::cell::UnsafeCell<T>`][UnsafeCell] (`!Sync`のみ)

[pointer]: https://doc.rust-lang.org/std/primitive.pointer.html
[Rc]: https://doc.rust-lang.org/std/rc/struct.Rc.html
[UnsafeCell]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html

また`UnsafeCell<T>`をフィールドとして持つ`std::cell::Cell<T>`や`std::cell::RefCell<T>`なども`Sync`を実装しません。




注意点として、自動的に実装された（あるいはされていない）OIBITはrustdocに表示されません[^10]。つまり、後方互換性を保ちながらメンバを追加できるか判断したいときにドキュメントを判断基準にできないのです。標準ライブラリの型に限って言えば、幸いなことに`Send`や`Sync`を実装しない型には明示的に`impl !Send`のように書かれているのでその旨がドキュメントに表示されます[^11]。しかし、これは現時点でunstable featureを必要とするため、サードパーティのライブラリでは気軽に行えない状況です[^12]。



[^10]: ~~これに関するissue ([rust-lang/rust#17606](https://github.com/rust-lang/rust/issues/17606))はあるが、あまり活発に議論されていないようである。~~
[^11]: ~~[実装例](https://doc.rust-lang.org/1.22.0/src/alloc/rc.rs.html#283-294)（ここで`ptr::Shared: !Send + !Sync`を満たすから明示的に書かずとも自動で実装されるはずであることに注意）。[表示例](https://doc.rust-lang.org/std/rc/struct.Weak.html#implementations)~~
[^12]: ~~例えば`tokio_core`の[`reactor::Core`](https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Core.html)は、実装を見ると[`Rc`を含んでいる](https://docs.rs/tokio-core/0.1.10/src/tokio_core/reactor/mod.rs.html#55)から`Send`も`Sync`も実装していないことが分かるが、ドキュメントにはこれに関して何の記述もない。~~

**追記 2018-3-22**: 最新のrustdoc nightly（6728f21d8、2017-11-23以降）では`Send`と`Sync`の実装の有無が各型のドキュメントの"Auto Trait Implementations"という特別のセクションに表示されます[^13]。ただし、現行の安定版（1.24.1）では自動的に実装された（あるいはされていない）auto traitはドキュメントに表示されません。

[^13]: 表示例: [hyper::header::Headers - Rust](https://docs.rs/hyper/0.11/hyper/header/struct.Headers.html#synthetic-implementations)。`Send`と`!Sync`が表示されている。

ある型にauto traitが確実に実装されていることを保証する方法としては、以下のようにテストに実際に`Send`や`Sync`を必要とするコードを書くことが考えられます。

```rust
use super::Foo; // `Send`かつ`Sync`であることを保証したい型

fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}

#[test]
fn test_thread_safety() {
    assert_send::<Foo>();
    assert_sync::<Foo>();
    // `Foo: !Sync`の場合（`Foo`が`RefCell<()>`をフィールドに持つ場合）の例:
    // error[E0277]: the trait bound `std::cell::RefCell<()>: std::marker::Sync` is not satisfied in `Foo`
}
```

また、一度auto traitを実装してしまうと元に戻すのがメジャーチェンジになってしまうため、auto traitが実装されていないことを保証したいケースもあるかと思います。そのような場合は[`compiletest_rs`]クレート（私は試していませんが）やRustdocの[`compile_fail`]テストが役に立つかもしれません。

[`compiletest_rs`]: https://crates.io/crates/compiletest_rs
[`compile_fail`]: https://doc.rust-lang.org/rustdoc/documentation-tests.html#attributes

### `Deref<Target=任意>`を実装する型に固有メソッドを追加する

`Deref<Target=Bar>`を実装する型`Foo`にメソッド`Foo::hoge`を追加することを考えます。このとき、`Bar`も同様にメソッド`Bar::hoge`を持っていた場合、従来はメソッド構文で呼び出せていた`Bar::hoge`が`Foo::hoge`によって上書きされてしまいます（`(*foo).hoge()`のようにして呼び出すことは引き続き可能）。

このことは、`Foo`が例えば任意の型にディレファレンスするコンテナ型`Foo<T>`であるような場合だと大きな問題になります。よって、任意の型を`Deref`の`Target`とするような型に固有メソッド（inherent method）を追加するのは一般的に避けるべきです。例えば`Box::into_raw`はメソッドではなく関連関数として実装されています。

## 破壊的変更の緩和策

### The semver trick

Semver trick（あるいはdtolnay trick）とはRust library teamのDavid Tolnay氏が[dtolnay/semver-trick: How to avoid complicated coordinated upgrades][dtolnay/semver-trick]で発表した、ライブラリのアップデート時の影響を最小限にとどめる手法です。

[dtolnay/semver-trick]: https://github.com/dtolnay/semver-trick

実例として、ライブラリクレート`lib`がバージョン`0.1.0`であるとして、`lib::Foo`にメジャーチェンジを加えてバージョン`0.2.0`をリリースすることを考えます。このとき`lib::Bar`には特に変更を加えていないにも関わらず、バージョン`0.1.0`の`lib::Bar`とバージョン`0.2.0`の`lib::Bar`が別の型として扱われてしまい、このままでは互換性が崩れてしまいます。

そこでバージョン`0.1.0`の`Cargo.toml`の`[dependencies]`に`lib = "0.2"`を書き加えて、さらに`lib::Bar`の実態を`extern crate lib; pub use lib::Bar;`に書き換えて、これをバージョン`0.1.1`としてリリースします。こうすることでバージョン`0.1.1`の`lib::Bar`とバージョン`0.2`の`lib::Bar`は同じ型として扱われるようになり、破壊的変更が最小限にとどまります。

### Rust API guidelinesを読む

[rust-lang-nursery/api-guidelines]はRust teamによるAPI設計についてのガイドラインです。このうち[future-proofing.md]が破壊的変更を防ぐための賢い方針をいくつか解説しています。また、このガイドラインは互換性の問題に限らず広くAPIの設計に関するベストプラクティスが詰まっているので一読をお勧めします。

**追記 2018-1-6**: 非公式の日本語訳もあります:
[概要 - Rust APIガイドライン](https://sinkuu.github.io/api-guidelines/)

[rust-lang-nursery/api-guidelines]: https://github.com/rust-lang-nursery/api-guidelines
[future-proofing.md]: https://github.com/rust-lang-nursery/api-guidelines/blob/master/src/future-proofing.md

### 誤ってリリースしてしまったときはyankを使う

マイナーリリースにメジャーチェンジを入れてしまった、という最悪のケースには[yank]が有効です。Yankはリリースの取り消しと似たような働きをします。crates.ioからyankされたクレートのバージョンはアプリケーション側で`cargo update`によって新たに`Cargo.lock`に追加されなくなります（ただし既に追加されているものは取り消せません）。

[yank]: http://doc.crates.io/crates-io.html#cargo-yank

例えばあるクレートのバージョン`1.0.1`をyankするには以下のコマンドを実行します。

```shell
$ cargo yank --vers 1.0.1
```

これでこのクレートのバージョン`1.0.1`に新たにアプリケーションが依存することを防げます。

## バージョン1.0.0をリリースする前に

ライブラリが十分安定したらバージョン`1.0.0`をリリースしたいところです。ただ、一度メジャーバージョンが`1`になるとメジャーチェンジを行う手段がメジャーリリースしかなくなってしまうため、バージョン`1.0.0`のリリースにはおのずから慎重さが求められます。

近い将来にメジャーチェンジが予見される場合はバージョン`1.0.0`はリリースしないべきです。特にpublic dependencyが安定していない（バージョンが`<1.0.0`）場合は当該の依存クレートがアップデートした時点で自ライブラリにもメジャーチェンジを加える必要が出てしまうのでAPIを安定させるには早すぎます（Rust API guidelinesの[C-STABLE]）。また、public dependencyでなくとも上述の`links`キーを含むクレートも同様に扱うべきでしょう。

[C-STABLE]: https://github.com/rust-lang-nursery/api-guidelines/blob/master/src/necessities.md#public-dependencies-of-a-stable-crate-are-stable-c-stable

また、将来のRustの機能追加がAPIの設計に影響を及ぼす可能性も検討するべきです。例えば`bitflags`クレートがassociated constantsがstableになるまでバージョン1のリリースを待ったという例があります（[issue rust-lang-nursery/bitflags#80]）。

[issue rust-lang-nursery/bitflags#80]: https://github.com/rust-lang-nursery/bitflags/issues/80

## 終わりに（愚痴）

この記事ではクレートの互換性について述べましたが、この中でもとりわけ私が実際にライブラリを保守する上で問題になったのが`ring`クレートの`links`キー周りの非互換性でした。そのため本文もこのあたりの内容が気持ち多めになっています。

`ring`クレートがアップデートするたびに、依存する`ring`のバージョンの違いによって周辺のクレート群が分断されて相互に依存できなくなるのでつらいです。自ライブラリの依存の中に古い`ring`に依存するクレートがあると、自ライブラリの`ring`のバージョンも上げられなくてつらいです。また、依存する`ring`のバージョンを変更することがメジャーチェンジなので気軽に手を出せない点もつらいです（つらい）。

Pure Rustでないライブラリにはこういうつらみがあるので、もっとRustが流行って何もかもpure Rustで解決できる世界になれば良いと思います。