🌏

Dart Metadataについて

2024/10/19に公開

概要

dart macrosの登場などで
改めてDartMetadataについておさらいしていこうと思います。

※FlutterKaigi2024のプロポーザルに落選したため、供養するためにも記事として簡単にまとめ記載しました。
https://fortee.jp/flutterkaigi-2024/proposal/654eb8f7-2ae5-478e-8ef5-c02b60f7396f

メタデータとは

※以下抜粋

コードに関する追加情報を提供します。メタデータ注釈は@文字で始まり、その後にコンパイル時定数 (deprecatedなど) への参照または定数コンストラクターへの呼び出しが続きます。

要は「コードに対して追加情報を付与できる」というもの

dart:core

以下、dart:coreに含まれているMetadata

  • @Deprecated
  • @pragma
  • @override

普段からDartを利用している人には馴染みがあるものかと思います。

@Deprecated

注釈した機能は非推奨としてマークされます。
deprecatedも以前はありましたが、現在は推奨されていないようです。
https://dart.dev/tools/linter-rules/provide_deprecation_message

非推奨であるため、機能自体は正常に動作はしますが
将来ある時点で削除されるものとなるため、早期に代替え手段を選ぶ必要があります。
@Deprecatedを付与した開発者は、メッセージで代替え案を文書化して提示する必要があります。

const MaterialApp({
...
...
    (
      'Remove this parameter as it is now ignored. '
      'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
      'This feature was deprecated after v3.7.0-29.0.pre.'
    )
    this.useInheritedMediaQuery = false,

上記MaterialAppで定義されているuseInheritedMediaQueryを例にとると

  • 削除理由
  • 代替え案
  • どの時点で非推奨か

をメッセージにて的確に文書化しています。

@pragma

@pragmaは特定のコンパイラの最適化や動作に対するヒントを提供するためのアノテーション。
Dart VMなどの説明は、本記事での目的とは乖離するため、気になる方はこちらをご参照ください。

@pragmaを利用する際に、一番見かけるのは以下かなと思っております。

('vm:entry-point')

FCMのBGハンドラーを適切に実行する際に必要とされるエントリーポイントとなります。
公式ではリリースモードのツリーシェイキングで削除される可能性がありますと記載があり、
Dart VMのエントリーポイントに認識されないと、適切な処理が行われないものということになるそうです。
vm:entry-point以外にも様々なプラグマが存在しているため、気になる方は以下を参照ください。
https://mrale.ph/dartvm/pragmas.html

@override

interface memberをオーバーライド(上書き)する際に用いるアノテーション。
OOPをご存知の方からしたらお馴染みのものになるかと思います。

abstract class Fuga {
  int get age;
  String get name;
}

class Hoge implements Fuga {
  
  int get age => 30;

  
  String get name => '太郎';
}

修飾子に関してはこちらでも触れているため気になる方はご参照ください。
https://zenn.dev/masakunn/articles/a56cd8dc4adefd

meta library

開発者がソースコードを静的に分析するだけでは推測できない意図を表現するために使用できる注釈。
dart2js / meta / meta_metaなどが同包されているが、今回はmetaについて以下を触れていきたいと思います。

  • @doNotStore
  • @factory
  • @immutable
  • @internal
  • @literal
  • @nonVirtual
  • @protected
  • @redeclare
  • @reopen
  • @required
  • @sealed
  • @useResult
  • @visibleForTesting

@doNotStore

トップレベル変数として定義している値に格納されることを許容しない。


int increment(int age) {
  return age +1;
}

final v = increment(0); // ×

https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=invalid_annotation_target#assignment_of_do_not_store

@factory

インスタンスまたは静的メソッドに注釈を付けるために使用される。
名前付きコンストラクターがこれに当たります。
内部的(暗黙的)に名前付きコンストラクターはこちらを付与されているということになりますので
明示的に付与することは基本的にはしないものになります。

class Hoge {
  Hoge();
  
  
  Hoge.fuga();
}

⭕️

class Hoge {
  Hoge();
  
  Hoge.fuga();
}

@immutable

不変であることを明示的に指定するもの。
クラスに付与されている場合は、クラス内のフィールド変数がすべてfinalでなければならない。
可変となる変数を持つことを許容しないものとなります。
@immutableを付与しているクラスを継承しているクラスも不変でなければならなくなる。


class Hoge {
  Hoge(this.name);

  final String name;
  int age = 0;
}

⭕️


class Hoge {
  Hoge(this.name, {this.age = 0});

  final String name;
  final int age;
}

@internal

宣言されているパッケージ内からのみ使用し、
そのパッケージのパブリックAPIから公開してはならない宣言に注釈を付けるために使用される。
要はプライベートなクラスとして注釈させるもの。
ただし、analyzerはこれを推奨しておらず、プライベート宣言の場合は_を用い
そうでない場合は@internalを付与しないクラスとして宣言することを推奨しています。
https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=invalid_internal_annotation#invalid_internal_annotation

@literal

コンストラクターへの引数の1つ以上がコンパイル時定数でない限り、コンストラクターの呼び出しでキーワードを使用する必要があることを示します

要は、constを付与したクラスでない限り、@literalを付与できません。
逆を言えば、@literalを付与しているクラスはconstを付与しないといけません。

@nonVirtual

mixin内のインスタンスメンバーに注釈を付けるために使用。
mixinを継承しているクラスでは@nonVirtualを付与しているメンバーに対してオーバーライドをしてはいけないという注釈を付与する。

mixin Fuga {
  
  String string() {
    return '';
  }
}

class Hoge with Fuga {
    // overrideしてはならない
   String string() {
     return '';
   }
}

⭕️

mixin Fuga {
  
  String string() {
    return '';
  }
}

class Hoge with Fuga {
}

@protected

アノテーションのつけられたメンバはサブクラスとライブラリ内でのみ表示されるようになる。

class Hoge {
  
  void doSomething() {
  }
}

別ファイルでdoSomethingの呼び出しが不可能となる。
プライベートメンバと同様な効果が得られる模様。

@redeclare

再宣言されたメンバーに注釈を付ける。
analyzerで不正とはなりはしないが、意図せず再宣言された際に、開発者が意図を汲み取れるような仕組みとして用意されたもの。
https://dart.dev/tools/linter-rules/annotate_redeclares

@reopen

暗黙的にクラスを再開させないためのもの。
修飾子を使用再オープンの制御などは実現可能だが、実装者が意図して制御を緩め暗黙的にクラスを再オープンする可能性があるため、@reopenを付与することを推奨している。
※現在は実験段階でDart3.0以降から利用可能。
https://dart.dev/tools/linter-rules/implicit_reopen

@required

Dart 2.12以降では、組み込みのrequiredキーワードを使用して、名前付きパラメータを必須としてマークする必要があるため、@requiredは基本利用しない方針となっている。

@sealed

スーパータイプとして使用できないクラスをマークする注釈として利用。
Dart3.0以降では組み込みのクラス修飾子としてsealedを利用可能となったため、こちらを利用することを推奨している。

@useResult

特定の関数が返す結果を必ず使用するべきであることを示すために使われる。

class Hoge {
  Hoge();

  
  int increment(int age) {
    return age +1;
  }
}

final hoge = Hoge();
hoge.increment(0); // 返り値があるにも関わらず利用していない

⭕️

class Hoge {
  Hoge();

  
  int increment(int age) {
    return age +1;
  }
}

final hoge = Hoge();
final age = hoge.increment(0);

@visibleForTesting

テスト可能なコードとして注釈する。
テスト用に公開されたメンバーであることを示すことで、開発者にその意図を伝え、コードの可読性を向上させ、通常のコードでは使用すべきでないことを示唆することが可能。

まとめ

調べた限りでも興味深いMetadata annotationが色々ありました。
言語仕様が変化していき役割を終えたものも複数ありますが、現在でも利用可能なものは多々あるため
適切に利用し、保守性を保てたらと感じています。

今後dart macrosの登場で、この辺りがどのように変化していくかも
個人的には気になる点ではあります。

参考

https://api.dart.dev/stable/3.5.4/dart-core/dart-core-library.html
https://api.flutter.dev/flutter/meta/meta-library.html#constants
https://mrale.ph/dartvm/

Discussion