Swift Digest
SE-0087 | Swift Evolution

Rename lazy to @lazy

Proposal
SE-0087
Authors
Anton3
Review Manager
Chris Lattner
Status
Rejected

01 何が問題だったのか

Swift には、プロパティの初期化を最初のアクセス時まで遅らせる lazy という機能があります。次のように var の前に書いて使います。

struct ResourceManager {
  lazy var resource: NSData = loadResource()
}

ここで resource の型は NSData のままで、lazy は「初期化のタイミングを遅らせる」という初期化時の副作用だけを変えます。値そのものは NSData であり、取り出し方や書き換え方も通常の var と変わりません。

一方、Swift には宣言に付ける修飾として キーワードlet / var / weak / mutating など)と 属性@available / @objc / @nonobjc / @NSCopying / @NSManaged / @IBOutlet など @ 付きのもの)の二つの形式があり、ゆるやかに役割分担しています。キーワードは変数の型や保持のされ方そのものを変えるものが多く、属性はそれ以外のメタ情報(ABI、Objective-C との相互運用、Interface Builder との結び付き、など)を付与するものが多い、という住み分けです。

その観点で見ると、lazy は「初期化のタイミングを後ろにずらす」という性質の宣言修飾であって、プロパティの型やストレージの仕組みを表に出すものではありません。似た立ち位置にある @NSCopying@NSManaged@IBOutlet が属性として提供されているのに対し、lazy だけがキーワードとして特別扱いされているのは不揃いに見える、というのがこの提案の問題意識でした。

加えて、当時検討されていた Property Behaviors(後の Property Wrappers に相当する構想)では、lazy のような「プロパティに付随する挙動」をライブラリとして定義できるようにする方向が議論されていました。その世界では lazy も標準ライブラリ側のユーザ定義挙動として提供されるのが自然で、そのためにはまず構文を属性側(@lazy)に寄せておく必要がある、というのが提案の背景にあります。

02 どのように解決されるのか

この提案は Rejected(却下) となりました。lazy は現在の Swift でも引き続きキーワードとして提供されており、@lazy という属性は導入されていません。

// 現在の Swift でもこの書き方のまま
struct ResourceManager {
  lazy var resource: NSData = loadResource()
}

以下では、提案されていた内容と却下の経緯を整理しておきます。

提案されていた内容(採用されなかったもの)

提案の本体はごくシンプルで、次の二点だけでした。

  • lazy キーワードを廃止し、同じ役割を持つ属性 @lazy を追加する。
  • 既存コードは lazy var@lazy var に機械的に置き換えるマイグレーションで移行する。

もし採択されていれば、利用側は次のような書き方に変わっていたはずです。

// 採択されていた場合のイメージ
struct ResourceManager {
  @lazy var resource: NSData = loadResource()
}

@lazy の振る舞い自体は現行の lazy と同じで、最初のアクセスまで初期化式の評価を遅らせる、というものです。つまり利用者にとっての体験はほぼ変わらず、「修飾の書き方がキーワードから属性に寄せられる」という見た目の整理が主眼でした。

却下の経緯

却下の主な理由は、「@lazy への書き換えは破壊的変更である割に、利用者が得られる利益が小さい」と判断された点にあります。lazy は初期化の挙動そのものを変える修飾で、キーワードとして扱ったほうがプロパティ宣言の先頭で目に入りやすく、コードレビュー時にも気付きやすい、という評価もありました。

また提案自身が Future Directions として、将来の Property Behaviors(ユーザ定義の宣言挙動)で lazy を標準ライブラリに切り出すための地ならしと位置づけていましたが、その Property Behaviors(SE-0030)側も Withdrawn となり、最終的には別形式の提案として Property Wrappers(SE-0258, Swift 5.1)として実装されました。Property Wrappers は @propertyWrapper を付けた型を @MyWrapper var x: T のように使う仕組みで、lazy 相当の挙動をライブラリ側で表現することも可能です。ただし lazy そのものを Property Wrapper に置き換える変更は行われておらず、lazy は現在もコンパイラ組み込みのキーワードのまま残っています。

結果として、lazy を属性化する方向性は Swift には取り込まれませんでした。今日の Swift では、lazy はキーワード、@MainActor / @Sendable / @objc などは属性、という住み分けが引き続き採用されています。