non-Void関数の未使用結果に警告が出るようデフォルト化する
Defaulting non-Void functions so they warn on unused results
このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。
01 何が問題だったのか
Swift 2 では、戻り値のある関数・メソッドを呼び出して結果を使わなくても、コンパイラは何も言いませんでした。たとえば次のコードは警告なしにコンパイルされ、加算の結果は黙って捨てられます。
4 + 5 // 警告なし
プレイグラウンドのように結果そのものを眺めたい場面を除けば、このようなコードはたいていバグです。結果を代入し忘れた、あるいは使う予定の値を書き漏らした、というケースが大半で、実プロジェクトでは発見されにくい不具合の温床になっていました。
「使ってね」を明示する既定
当時は逆に、「この関数の戻り値は使うべき」ということを @warn_unused_result というアトリビュートで明示する仕組みでした。付けなければ、戻り値を捨てても警告は出ません。
@warn_unused_result(mutable_variant="sortInPlace")
public func sort() -> [Self.Generator.Element]
これは主に、ミュータブル版とイミュータブル版の対(sort と sortInPlace など)を区別する用途で使われていました。しかし、戻り値を捨てたら警告すべきケースのほうが圧倒的に多く、既定が逆になっているのは実情に合いません。標準ライブラリやサードパーティのAPIを一つずつ @warn_unused_result で装飾していくのも、書き漏らしが起こりやすく現実的ではありませんでした。
スネークケース由来の不揃い
加えて @warn_unused_result や mutable_variant はスネークケースで、全体がローワーキャメルケースに統一されつつあったSwiftの中では浮いた存在でした。命名規則の観点からも、置き換えたい対象になっていました。
02 どのように解決されるのか
既定の向きを反転します。戻り値の型が Void ではない関数・メソッドを呼び出して結果を使わないと、コンパイラが警告を出すようになります。戻り値を捨てても問題ないAPIについては、宣言側で @discardableResult を付けることで警告を抑えます。@warn_unused_result は廃止されます。
基本の使い方
通常の戻り値つき関数は、結果を使わずに呼び出すと警告になります。
func g() -> Int { return 42 }
g() // 警告: 結果が使われていません
_ = g() // OK: 明示的に捨てる意図を示している
戻り値を捨てる呼び出しを正当な使い方として認めたいときは、宣言に @discardableResult を付けます。
@discardableResult
func f() -> Int { return 42 }
f() // OK: 手続き的に呼び出してよい
戻り値が Void の関数はそもそも対象外です。これまでどおり警告は出ません。
func h() { } // 対象外
呼び出し側での打ち消し方
APIに @discardableResult が付いていないときでも、呼び出し側で一時的に結果を捨てたい場合があります。そのときは従来どおり _ = を使います。
_ = g() // 意図的に捨てていることを明示
_ = は「結果を捨てる意図が呼び出し側にある」ことを示すコードレビュー上のマーカーとしても機能します。一方で @discardableResult は「APIの設計として捨ててよい」ことを宣言側で表明する手段です。役割が違うため、用途に応じて使い分けます。
関数値に代入したときの扱い
@discardableResult は関数の宣言に紐づく属性で、関数値の型自体には乗りません。そのため、変数に代入すると属性は引き継がれず、呼び出し時に警告が戻ってきます。
@discardableResult
func f() -> Int { return 42 }
let c1: () -> Int = f // OK
let c2: () -> Void = f // エラー: 戻り値型が合わない
let c3 = f // 代入で属性は失われる
c3() // 警告: 結果が使われていません
_ = c3() // OK
ミュータブル版・イミュータブル版の対応付け
@warn_unused_result には mutable_variant や message といった引数があり、sort() を結果未使用で呼んだら sortInPlace() を勧める、といった案内に使われていました。この役割はドキュメントコメントに移ります。具体的には、ドキュメントコメント側で MutatingCounterpart と NonmutatingCounterpart というフィールドを使って、対応する別名を紐づけます。コンパイラの検査ではなく、QuickHelpやコード補完側で扱われる情報になります。
C/Objective-C からインポートされたAPIの扱い
この提案の対象は純粋なSwiftコードです。ClangインポータでCやObjective-CのAPIをSwiftに取り込む場合、ソース側で ((warn_unused_result)) が付いていない関数には、自動的に @discardableResult 相当の扱いが与えられます。つまり、インポートされた関数の戻り値を捨てても、既定では警告は出ません。膨大な既存APIに対して一斉に警告が出ることを避けるための措置で、将来的に実コードへの影響を見極めた上で、インポートAPIにも既定を広げるかどうかは別途検討されます。
03 今後の見通し
戻り値型側への注釈
@discardableResult は関数の宣言に付ける属性ですが、本来「捨ててもよい」のは戻り値そのものなので、戻り値型側に注釈を付けるほうが自然ではないか、という案が示されています。
func f() -> @discardable T {} // f() のように手続き的に呼んでも警告なし
この方針には、関数値として取り回したり、カリー化したりした際に属性が引き継がれる範囲をどう設計するか、といった型システム上の検討事項が伴います。実装コストとのバランスから今回は採用されていませんが、将来強い動機が生まれた場合に再検討される可能性があります。
Objective-C 側からの注釈
CやObjective-CのAPIには、現状ではClangインポータが一律で @discardableResult 相当の扱いを与える方針になっています。これに対し、例外的に「戻り値を捨てたら警告したい」インポートAPIを、Objective-C 側のソースから明示的に注釈できるようにしたい、という要望が挙がっています。
型単位での既定変更
属性を関数だけでなく型にも付けられるようにし、その型のメソッドの既定挙動を切り替える、という案も話題になりました。メソッドチェーンを前提に設計された型のように、戻り値を捨てる呼び出しが日常的なケースで、型ごとに既定を寄せられるようにする狙いです。
いずれもProposal内で言及された方向性であり、将来の構想として示されているもので、実現を約束するものではありません。