1
Help us understand the problem. What are the problem?

posted at

updated at

Dartの型に関する誤解

はじめに

動機

本記事執筆時点で型安全導入から4年、Null安全導入から1年5ヶ月経過しているにもかかわらず、こともあろうかこれからDartを学ぼうとする者向けに、Dart 1.xまでの知識(または、その誤解)をベースとして解説する新規記事が散見されます。その一つへのコメントが元記事ごと消えてしまったので、ここで改めて纏めます。

Dartの型安全 (Null安全を除く)

Dartは2.0(SDK 2.0.0は2018/8/7リリース)で型安全になりました。
型安全はDart 1.x時代からオプションモード(Strong Mode)としてリリースされてきましたが、Dart 2.0で標準となると同時にSDK 2.0.0では型不安全(いわばWeak Mode)は削除されました。つまり、型安全モードが標準でかつ型不安全モードが併存するSDKバージョンは存在しません。Flutter以前のDart 人口は今と比べ物にならないほど少なかったので、明確な並行運用期間のないままこのような破壊的変更が可能だったと言えます。

DartのNull安全 (広義の型安全の一部)

Dartは2.12(SDK 2.12.0は2021/3/3リリース)でNull安全が導入されました。
続いてSDK 2.13.0ではNull安全が標準になりましたが、SDKのみならず言語仕様としてもNull不安全モードが現在(2.17)まで存続し、同一アプリ内での混在時の動作までも定義されています(unsound null safety機能)。これはFlutterの普及によりDart人口が急拡大し、もはや乱暴な破壊的変更が難しくなったことに対する解です(Language evolution | Dart)。ただし、Null不安全モードやその混在モード(unsound null safety)は明確に移行措置と位置づけられており、Dart 3.0では無くなるはずです。

予備知識

型安全

本記事ではクラスベースのオブジェクト指向言語を対象に、少なくとも値(=クラスインスタンス=オブジェクト)には型情報が有る前提とします(そうでない言語もあるが)。
その上で、ここでの型安全とは、変数にも型情報が有り、その初期化・代入時に右辺値(値、変数等)の型との互換性を検査(型検査)すること、とします。

動的型付けと静的型付け

型安全のための型検査を、動的型付け言語は実行時に行うのに対し、静的型付け言語はコンパイル時(デプロイ前または起動時等の実行前)に行います。
ちなみに、JavaSccriptはここで言うところの静的型付け言語でも動的型付け言語でもなく、型無し言語です。

健全な型安全

健全(Sound)な型安全とは、変数にその型(と互換)の型の値が入っていることを保証することです。
明示的キャスト等がある場合、健全な型安全には静的型付けと動的型付けの両方が必要となります。
私の知る限り、SwiftとDart 2.xが健全な型安全です。
TypeScriptやKotlinを含む他の多くの静的型付け言語は不健全(unsound)な型安全です。

メンバ解決 (蛇足)

静的型付け言語でもメンバの解決の多くは実行時に行われます(継承とポリモーフィズムがあるので)。

本題

誤解1: 型修飾を省略(varで宣言等)した変数は動的型付け

Dart 2.xは常に静的型付けです。
varを使った場合、その変数は型推論によって静的に型付けされます。
型推論のヒントが何も与えられなかった場合もObject?に静的型付けされます。
また、dynamicと型修飾するとその変数はdynamicという特殊な型に静的に型付けされます。
これは、Object?と型修飾するとその変数がObject?に静的に型付けされ、Objectとそのサブクラス型のインスタンス(とnull)(つまりなんでも)が代入できるのとよく似ています。
Object?との違いは、dynamicでは静的にも動的にも型検査しないことです。
Object?で型修飾した場合は型検査の結果、その変数経由で値のhashCoderuntimeTypenoSuchMethodtoStringOperator ==以外のメンバを評価しようとするとコンパイル時エラーですが、dynamicの場合は実行時にその変数経由で値のメンバ解決を試み、そこで失敗するとはじめて実行時エラーとなります。

Dart 1.xの型 (補足)

Dart 1.xは一部の例外を除いてJavaScriptと同じく型無し言語でした。
変数に任意で型修飾ができますが、これはあくまでIDEによるリコメンドやコンパラによる警告のためのものであって、コンパイル結果や実行時動作には全く反映されませんでした。
Dart 2.xが常に静的型付けであり、かつ必要に応じて動的型付けも行う健全な型安全であるのと極めて対照的です。
なお、一部の例外とはデバッグモードで動かした時で、動的型付け言語として動作しました。

健全性の意図的な穴 (補足)

dynamicは静的にも動的にも型検査しないので健全性の穴です。
これはJSON等の型無し言語とのインタフェースのための避難口として用意されています。
従って、何でも入る型として通常はObject?を利用すること、使うときにはキャストすることが推奨されています。
SwiftにおけるAnyも同様です(たぶん)。
プログラマに明示的にdynamicと修飾することを強制して意識させることで、dynamicがあることのみをもって言語全体が不健全とは位置づけていません。

誤解2: 全ての値はオブジェクトなので未初期化の変数はnull

Dartでは1等を含み全ての値がオブジェクト(クラスインスタンス)です。
また、変数が保持するのはオブジェクトへの参照です。(ここまでは良い)
しかし、v == nullvが何も参照しない状態ではなく、Nullクラスのインスタンスnullへの参照を保持する状態です。
また、constfinalな変数では暗黙の初期値が未定義であり、finalな変数を初期化前に評価(null比較を含む)するとコンパイル時エラーです。

Dart 2.10まではconstでもfinalでもない変数は暗黙的にnullへの参照で初期化されていました。

しかし、Null安全を導入したDart 2.12以降はint等はNull不可型であり、その暗黙の初期値は未定義であり、constfinalと同様に初期化前に評価(null比較を含む)すると多くの場合コンパイル時エラーです。
なお、late finalとすると初期化前の評価エラーは実行時に検出されます。

つまり、Null可型(例: int?)だがconstでもfinalでも無い変数(例: int? v;)だけが暗黙の初期値をもち、それがnullです。

最後に

Dart、Flutterは公式ドキュメントが充実していることで評価されています。
私の記事を含めて間違っている可能性のある二次情報にあたる前に、公式ドキュメントを読むことを強く推奨します。
英語しかありませんが、ソフトウェアエンジニアは現実的に英語を避けて通れないものと諦めて、または英語を学ぶ絶好のチャンスと前向きに捉えて、公式ドキュメントを読みましょう。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
1
Help us understand the problem. What are the problem?