Dart Macrosの基礎と活用例

この記事は[Commune Advent Calendar 2024](https://qiita.com/advent-calendar/2024/commune)シリーズ2の14日目の記事です。

# Motivation

Dart Macrosについて、様々な方が記事を書いたり、またカンファレンスで取り上げられたりなど、注目度が高まっているように感じます。

私もネット上に出回っている情報を見て、ざっくりと概要を把握はしていますが、改めてMacrosがどのようなものか、開発者にとってどのような変化があるかなどを詳細に調べ、Macrosに対する理解を深めていきたいと思います。

# Macrosとは

公式Docs上に、MacrosのDocsが存在しており、次のような記載があります

> The Dart macro system is a major new language feature currently under development which adds support for static meta-programming to the Dart language. A Dart macro is a user-definable piece of code that takes in other code as parameters and operates on it in real-time to create, modify, or add declarations.

https://dart.dev/language/macros

また、今年投稿された Announcing Dart 3.4 の記事には、次のような記載があります

> To improve the development experience in Dart, macros provide a metaprogramming solution, like code generation. This solution is built into the Dart language to give developers maximum performance, efficiency, and productivity.

https://medium.com/dartlang/dart-3-4-bd8d23b4462a

さらに、GithubにはMacrosの仕様に関する詳細なドキュメントが存在おり、次のような記載があります

> A macro is a piece of code that can modify other parts of the program at compile time. A macro application invokes the given macro on the declaration it is applied to. The macro introspects over the declaration it was applied to and based on that generates code to modify the declaration or add new ones.

https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md

これらの情報から、以下の点がわかります。

- MacrosはDartの開発ロードマップの中でも重要な位置づけを持つ機能である
- Macrosはstatic meta-programmingの手法を利用しており、開発者体験を向上させることを目的としている
- Macrosはパラメータを受け取り、それに基づいてコンパイル時にリアルタイムでコードを生成する仕組みである

関連するドキュメントを読んでいると、キーワードとして、"static meta-programming" というワードが出てきたので、それについて見ていきます。

# static meta-programmingとは

Dartにおけるstatic meta-programmingを説明しているspecificationがGithub上に存在し、以下の記述があります。

> Metaprogramming refers to code that can do this—code that operates on other code as if it were data. It can take code in as parameters, reflect over it, inspect it, create it, modify it, and return it. Static metaprogramming means doing that work at compile-time. This avoids the safety, performance, and code size problems of runtime metaprogramming.

https://github.com/dart-lang/language/blob/main/working/macros/motivation.md

この記述から、"static meta-programming"とは、コンパイル時に、パラメータを受け取り、コードを反映、検査、作成、変更し、返すことを意味していることがわかります。この手法は、ランタイム時に比べて、安全性、パフォーマンス、コードサイズの問題を回避できる点が特徴です。

> Examples are the C preprocessor, C++ templates, Rust macros, Swift function builders, and compile-time execution in Zig.
> 

私は上記いずれの言語も経験がないですが、上記の言語にはこの概念が適用されている機能があるようです。

# 活用例

Macrosの仕様書に記載されているサンプルケースによると、次のケースがMacrosを使うサンプルケースとして紹介されています。

https://github.com/dart-lang/language/blob/main/working/macros/motivation.md#sample-use-cases

- JSON serialization and deserialization
- Data classes
- Flutter verbosity

上記はあくまで代表例で、Flutterで開発をしていて、ボイラーテンプレートと思われる部分に関しては、macrosが利用できる可能性はあると思います。

公式ドキュメントの中では、The JsonCodable macro の例が記載されているので、実際に手元で動かしてみます。

https://dart.dev/language/macros

# The JsonCodable macroを動かしてみる

手元のFlutterプロジェクトで、macrosを有効化して、以下のようなコードを書きます。

```dart
import 'package:json/json.dart';

@JsonCodable() // Macro annotation.
class User {
  final String id;
}
```

すると、以下のコードが、自動生成されます

```dart
part of 'package:test_macros/user.dart';

import 'dart:core' as prefix0;

augment class User {
  external User.fromJson(prefix0.Map<prefix0.String, prefix0.Object?> json);
  external prefix0.Map<prefix0.String, prefix0.Object?> toJson();
  augment User.fromJson(prefix0.Map<prefix0.String, prefix0.Object?> json, )
      : this.id = json[r'id'] as prefix0.String;
  augment prefix0.Map<prefix0.String, prefix0.Object?> toJson() {
    final json = <prefix0.String, prefix0.Object?>{};
    json[r'id'] = this.id;
    return json;
  }
}
```

Userクラスに変更を加えると、以下のようにリアルタイムに自動生成ファイルが更新されます。

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124924/cdd99c4e-1b85-686f-3c55-71fd7268457c.gif)

ほぼリアルタイムでストレスなくコードが生成でき、開発者体験は非常に高いと感じました。
生成されたコードに関して、"augment" というワードが出てきました。筆者はあまり触れたことがないワードなので、それについて見ていきます。

# Augmentationsとは

Augmentationsについて、Githubに仕様に関する詳細なドキュメントが存在おり、次のような記載があります。

> Augmentations allow spreading your implementation across multiple locations, both within a single file and across multiple files. They can add new top-level declarations, inject new members into classes, and wrap functions and variables in additional code.

https://github.com/dart-lang/language/blob/main/working/augmentation-libraries/feature-specification.md

端的に説明すると、Macrosはコンパイル時にコードを生成する役割を担い、Augmentationは生成されたコードを元のプログラムに適用する役割を担います。

前述のMacrosの例では、Macrosを用いて作成されたaugment User Classが、元のUser ClassにtoJson関数とfromJson関数を追加しています。

# 終わりに

公式の仕様書を読みつつ、簡単なコードをMacrosで生成することを通じて、Macrosの概要を見て見ました。

Dart MacrosはDartにおけるコード生成と適用のための強力なツールで、これらの仕組みは、特にFlutter開発においてボイラープレートの削減やコードの自動生成を実現し、開発体験を大きく向上させる可能性があると思います。

タイムラインによると、2025年にstableリリースをするべく開発を進めているみたいなので、引き続き動向を追っていきたいです。