Null不安全モードは **2023年中旬** Dart 3.0で削除 ~~か?~~

## 要約 (追記)
言語バージョンはSDKの言語モードを決定します。
破壊的変更を含む時に特に言語モードが重要です。
パッケージやライブラリが古い言語バージョンを指定することで互換モードで動作します。
言語のマイナーバージョンは新しい言語モードを追加する時にインクリメントされます。
古い言語モードを削除する時に言語のメジャーバージョンがインクリメントされます。
Null安全モードはマイナーバージョン2.12で追加されました。
これは、Null不安全モードがメジャーバージョン3.0で削除されることを示唆しています。
~~ただし、3.0が具体的にいつになるかはわかりません。~~
したがって、計画的なNull-safetyモードへのマイグレーションが重要です。
**2023年中旬リリース予定の3.0でNull不安全モードが削除されることが宣言されました**

## 前置き
~~はじめにお断りしておくとタイトルは釣りです。~~

### Null Safety: Sound non-nullable types with incremental migration (NNBD)とは
Null Safety: Sound non-nullable types with incremental migration (NNBD)の復習は基本的に別記事に譲り、ここでは本題に関係がある部分に限定して「Sound」「Null-safety」「Incremental Migration」の言葉をそれぞれ掘り下げていきます。

#### Soundのメリット
`null`がNull不可型の変数、クラスメンバ、インスタンスメンバ、パラメタ他(変数等)に代入されないことを保証できます。それによって変数等の実行時`null`チェックが不要となり、より小さいコードで高速に実行することができます。当たり前のようですが、これができているのはDartを除いてはSwiftぐらいで、TypeScriptやC#の処理系は実行時の`null`チェックを強いられています。

また、`final`で`extends`不可な`int`等であれば最適化がunboxしてスタック等で値を引き回すことも可能となります。Javaのようなプリミティブ型を持たないDartにとってこの最適化はことさら重要です。実は、JIT最適化は以前から`int`をunboxしていましたが、FlutterのようなAOT環境のアプリにとってはやはり重要な特性です。

#### Null-safetyの互換性
Dartの「Null-safeモードは」上位非互換(breaking change)です。つまり、過去のDartプログラムは無修正のままNull-safeモードで実行できるとは限りません(後方非互換)。

#### Incremental Migrationの単位
Incremental Migrationの最小単位はライブラリです。つまり、ひとつのDartプログラム内でNull-safeなライブラリとNull-unsafeなライブラリを混在させることができます。

ただし、混在した場合のNull-safeなライブラリの動作はUnsoundなNull-safeモードとなります。そうすると、先述のSoundのメリットを享受できなくなりますので、Pubパッケージ開発者コミュニティを巻き込んだNull-safe完全移行が究極の目標にならざるを得ません。

### 利用可能な言語機能の指定 (再修正)
言語バージョン指定(Dart Language Versioning)およびそのライブラリ別言語バージョン選択(Per Library Language Version Selection)はSound Null-safety with Incremental Migrationが動機となり開発されました。
本機能は、expremental等のフラグで管理しない機能、特に上位非互換な機能で意義の大きいものです。SDKの複雑化防止のために言語バージョンでリニアに、一定期間(バージョン間)だけライブラリから利用を抑止できる新機能を管理します。

バージョンの関係は下記の様になります。

```
SDKのバージョン ① (例: 2.13.1)
≒ SDKが提供する最大言語バージョン ② (例: 2.13)
　≧ パッケージが課す最小SDKバージョン制約 ③ (例: 2.12.0-0 ※Null安全β ※「蛇足」参照)
　≒ パッケージ内のライブラリが要求する言語バージョンのデフォルト ④ (例: 2.12)
　　≧ パッケージ内のライブラリが要求する言語バージョン ⑤ (例: 2.10 ※Null不安全)
　　　≧ ②と同じ言語メジャーバージョン内の最小言語マイナーバージョン ⑥ (例: 2.0)
　　　　≧ SDKが提供する最小言語バージョン ⑦ (例: 2.0)
```

①②⑥⑦は利用するSDKで決まります。

③はDart 2.12から`pubspec.yaml`に記載することが必須となりました。
SDKバージョンはメジャー.マイナー.パッチの3要素ですが、言語バージョンとの比較・変換ではパッチを用いません。
ただし、例外として下記のようにテストバージョン(β等)を指定して対応する正式には未公開の言語バージョン(例: Dart 1.2β)を有効化することもできます。

```yaml
environment:
  sdk: '>=2.12.0-0 <3.0.0'
```

⑤についてはDart 2.8から各ライブラリの先頭で`// @dart = 2.10`のように指定できますが、任意です。省略した場合はそのライブラリが暗黙的に要求するバージョンとして④が採用されます。`// @dart = x.y`には④以下のバージョンしか指定できませんので、これにライブラリ個別に旧互換モードを選択し、マイグレーション時間を稼ぐ以外の使い途はありません。

⑤は当然ながらSDKが提供する最小言語バージョン⑦以上でなければなりません。⑦は②と同じ言語メジャーバージョンの下限⑥以下であることが保証されているので、⑤にも少なくとも②と同じ言語メジャーバージョンの下限⑥までは遡って指定できます。

⑦は①から離れているほどSDKの複雑さが増します。SDKがどこまで小さい(古い)言語バージョンを提供するかはDartチーム次第ですが、メジャーバージョンアップ(例: 2.x→3.0)直後もその下限(例: 3.0)を下回ることはないのではないでしょうか。`SDKが提供する最小言語バージョン⑦ ＝ ②と同じ言語メジャーバージョン内の最小言語マイナーバージョン⑥`と考えるべきかもしれません。

### Null-safetyサポートのバージョン名は何故3.0ではなかったか?
Dart 2.12(Flutter 2.0)でNull-safetyを正式サポートしています。

Dart 2.0においてそれまでの動的型付け言語から大転身を遂げた静的型付け機能(Strong Mode)以降では、Null-safetyが最大の非互換機能拡張です。そうでありながらそのバージョンは3.0を名乗っていません。というのは、Null-safetyをサポートする際には従来のNull-unsafeモードと混在可能なUnsound Null-safety機能を用意したので、Dart 3.0を名乗る必要がなかったということです。

なお、2.13では`dart create`による新規パッケージでNull-safetyが標準になりましたが、Unsound Null-safe機能が維持されたことに変わりはありません。

## 本題
長くなりましたがようやく本題です。

### Dart 3.0でNull-unsafeモードを削除か?
従来のNull-unsafeモード(と共存可能なUnsound Null-safe機能)を持つバージョン名として、たとえその必要がなかったとはいえ、SDK 3.0.0を選んでも良かったはずです。そこを、敢えてSDK 2.12.0を選んだということは、SDK 3.0.0をNull-unsafeモード(とUnsound Null-safe機能)の削除のタイミングと考えているということです。言い方はむしろ逆でしょうか。Dartチームが、いよいよNull-unsafeモード(とUnsound Null-safe機能)を削除しょうというタイミングで、そのバージョン名としてSDK 3.0.0を名乗る準備だった考えるべきでしょう。

なお、仮に他に重要な互換モードがあり、その削除がNull-unsefeモードの削除より先になる場合には上記の限りではありませんが、その可能性はありません。Dart 2.1から2.10(Null-safetyサポート前最終言語バージョン、2.11は欠番)までに他の重大な破壊的変更は有りませんし、今後あるとしてもリニアな管理なのでNull-unsafeモードが最初に破棄されるべき互換モードです。したがってSDK 3.0.0でNull-unsafeモードを破棄しない積極的理由がありません。

ちなみに、Weak Modeが破棄された最初のSDKバージョンは2.0.0でした。

### Null-nsafeモード削除の時期はいつか?
~~ではその時期はいつなのかと言うと、DartチームのSDK維持の都合が判断理由の中心なので、わからないとしか言いようがありません。
とは言え、EdgeのIE互換モードのようなことにはならず、1〜3年というところでしょうか。~~

追記2参照

## 結論
~~これまでの議論から導き出せる結論が「Null不安全が削除されるバージョン名はDart 3.0でほぼ確定だが、その時期はよくわからない」という壮大な肩透かしでしたので、~~ タイトルからそれて改めて結論を述べたいと思います。

### Xデーがやってくることは確実
ライブラリ別言語バージョン選択がバージョンでリニアに追加機能を管理するものであり、SDKが遡れる言語バージョンに制限を設けているので、Null-unsafeモードがいずれ削除されることは確定です。

### 計画的なマイグレーションを
一般的にSDK等の基盤の更新先延ばしはより重大な結果を招きます。計画的にNull-safetyにマイグレーションしましょう。

### 少なくともSDKは最新に
計画的にNull-safetyにマイグレーションできない場合も、少なくともSDKは最新に追随させましよう。これによって**Null-unsafeモードがdeprecatedになるタイミング**や削除されるタイミングで**SDKからメッセージを得ることができます**。何だそれだけか、と思ってはいけません。**計画的にマイグレーションできなかった人にとって、それ以外にマイグレーションのタイミングなど絶対に訪れないと思うべきです**。

## 追記1
パッケージが課す最小SDKバージョン制約③は下記のようにメジャーバージョンをまたいで1.y.zを指定できます。
```yaml
environment:
  sdk: '>=1.19.0 <3.0.0'
  flutter: ^0.1.2
```
これはSDK 2.y.zが言語バージョンがDart 1.19を提供するという意味ではなく、SDK 2.y.zが提供を保証する言語バージョンはあくまでもDart 2.0までですし、実際にSDK 2.0.0以降はDart 1.yを提供しません。また、Language Versioningが提供されたのがDart 2.7でしたので2.6.z以前のどのSDKを最低バージョンとして指定しても、利用できる言語機能はSDK 2.7.0を指定した場合と同じです。

それでもこのように指定する意味は、1.19.0より更に古いSDKを期待する古いパッケージが、うっかりStrong Mode(Dart 1.19提供開始)を前提としたFlutter関連パッケージを気づかぬまま呼び込まないようにすることにあります。

なお、Strong ModeはWeak Modeの上位互換ではありませんが、Strong Modeは概ねWeak Modeの下位互換、つまりStrong Modeに対応したコードは概ね前方互換でした。この特性を利用し、String Modeへのマイグレーション戦略は、Strong Modeをオプションモードで提供するSDK 1.19.0(2016/8/26提供)からSDK 2.24.x(2018/8/6終了)までの約2年間にPubパッケージを含みStrong Modeに完全移行するというものでした。これに追随できなかったPubパッケージはSDK 2.y.zから切り捨てられました。こんなことができたのは、Flutter 1.0以前のDart人口(や既存コード)が今に比べて圧倒的に少なかったからでしょう。

## 追記2
[Dart SDK 2.18.0のリリースノートブログ](https://medium.com/dartlang/dart-2-18-f4b3101f146c#618f "Dart 2.18: Objective-C & Swift interop | by Michael Thomsen | Dart | Aug, 2022 | Medium")にてDartのNull不安全モードが2023年中旬リリース予定のDart 3で終了することがアナウンスされました。あわせて不健全Null安全モードも終了し、健全Null安全モードのみとなります。より詳細は[[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk](https://github.com/dart-lang/sdk/issues/49530 "[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk")に記載してあります。

本記事の下記予測は順当に的中しました。
* メジャーバージョンは主要な破壊的変更の互換モードを削除するときに上げる
  * Null不安全モード(と不健全Null安全モード)の終了の言語バージョン名はDart 3.0(SDK 3.0.x)
* 時期はNull安全サポートの2021年3月3日を起点に1年から3年の間 (2023年7月リリースとすれば2年と4ヶ月)

加えて次の情報がありました。
* 健全Null安全マイグレーション済みの確認のために、Null不安全モード(と不健全Null安全モード)を封印(オプションフラグ可)したSDK 3.0.xのアルファを正式版数ヶ月前にリリース
* SDK 3.0.Xも要求するSDKバージョン上限が3.0.0未満のパッケージを許容する
ただしその場合も、パッケージが要求するSDKバージョンが2.12.0(Null安全導入)以上であることが条件

2つ目について、SDK 3.y.zは`pubspec.yaml`に下記のように書いてあるパッケージを読み込み対象とします。
```yaml
environment:
  sdk: '>=2.12.0 <3.0.0'
```
パッケージが要求する最低SDKバージョンは事実に基づく宣言なのに対し、上限SDKバージョンは(当時の)未来の予測に対する予防線でしかありません。メジャーバージョンアップは大きな破壊的機能の互換モードの削除目的であり、移行済みのパッケージにとっては予防線は杞憂の可能性が高いので、SDKが適切に無視するということですね。

## 追記3
SDK 2.19.0(Dart 2.19)で健全Null安全モードでない(Null不安全モードまたは不健全Null安全モードである)旨の警告(Info)が出ることになりました。
[[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk](https://github.com/dart-lang/sdk/issues/49530#issuecomment-1234284707 "[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk")

## 参考文献
[Language tour | Dart](https://dart.dev/guides/language/language-tour#important-concepts "Language tour | Dart")

[The pubspec file | Dart](https://dart.dev/tools/pub/pubspec#sdk-constraints "The pubspec file | Dart")

[Language evolution | Dart](https://dart.dev/guides/language/evolution#dart-28 "Language evolution | Dart")

[language/feature-specification.md at master · dart-lang/language](https://github.com/dart-lang/language/blob/master/accepted/2.8/language-versioning/feature-specification.md "language/feature-specification.md at master · dart-lang/language")

[Per Library Language Version Selection · Issue #94 · dart-lang/language](https://github.com/dart-lang/language/issues/94 "Per Library Language Version Selection · Issue #94 · dart-lang/language")

[Null Safety: Sound non-nullable types with incremental migration (NNBD) · Issue #110 · dart-lang/language](https://github.com/dart-lang/language/issues/110 "Null Safety: Sound non-nullable types with incremental migration (NNBD) · Issue #110 · dart-lang/language")

[Announcing null safety beta. Migrate your packages now! | Dart](https://medium.com/dartlang/announcing-dart-null-safety-beta-87610fee6730 "Announcing null safety beta. Migrate your packages now! | Dart")

[Announcing Dart 2.12. Sound null safety and Dart FFI ship to… | by Michael Thomsen | Dart | Medium](https://medium.com/dartlang/announcing-dart-2-12-499a6e689c87 "Announcing Dart 2.12. Sound null safety and Dart FFI ship to… | by Michael Thomsen | Dart | Medium")

[Announcing Dart 2.13. New type aliases language feature… | by Michael Thomsen | Dart | Medium](https://medium.com/dartlang/announcing-dart-2-13-c6d547b57067#93b6 "Announcing Dart 2.13. New type aliases language feature… | by Michael Thomsen | Dart | Medium")

[sdk/CHANGELOG.md at main · dart-lang/sdk](https://github.com/dart-lang/sdk/blob/main/CHANGELOG.md "sdk/CHANGELOG.md at main · dart-lang/sdk")

[Dart 2.18: Objective-C & Swift interop | by Michael Thomsen | Dart | Aug, 2022 | Medium](https://medium.com/dartlang/dart-2-18-f4b3101f146c#618f "Dart 2.18: Objective-C & Swift interop | by Michael Thomsen | Dart | Aug, 2022 | Medium")

[[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk](https://github.com/dart-lang/sdk/issues/49530 "[breaking change] Discontinue non-null-safe mode · Issue #49530 · dart-lang/sdk")