Swift Digest
SE-0255 | Swift Evolution

Implicit returns from single-expression functions

Proposal
SE-0255
Authors
Nate Chandler
Review Manager
Ben Cohen
Status
Implemented (Swift 5.1)

01 何が問題だったのか

Swift では、クロージャの本体が単一の式だけからなる場合に return を省略できます。たとえば、

let names = persons.map { $0.name }

のクロージャは { return $0.name } と書かずに済みます。

一方で、関数やプロパティのゲッタ、subscript、イニシャライザといった「関数的な」宣言の本体ではこの省略ができず、本体が単一の式であっても return を明示しなければなりませんでした。

extension Sequence where Element == Int {
    func sum() -> Element {
        return reduce(0, +)
    }
}

この sum() の実装では、本体の大部分が return というキーワードに費やされ、本来主役であるはずの式 reduce(0, +)return の後ろに押しやられています。クロージャではできる簡潔な書き方が、関数やプロパティではできないという不整合も生じていました。

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

関数・プロパティのゲッタ・subscript のゲッタ・イニシャライザの本体が単一の式だけからなるとき、その式は暗黙に返り値として扱われ、return を省略できるようになります。

基本的な使い方

先ほどの sum() は、return を省略して次のように書けます。

extension Sequence where Element == Int {
    func sum() -> Element {
        reduce(0, +)
    }
}

プロパティや subscript のゲッタでも同じように書けます。暗黙ゲッタ(get 自体を省略した形)でも、明示ゲッタでも使えます。

struct Point {
    var lat: Double
    var long: Double

    // 暗黙ゲッタ
    var location: Location { .init(latitude: lat, longitude: long) }
}

struct Echo<T> {
    // subscript の暗黙ゲッタ
    subscript(_ value: T) -> T { value }
}

struct GuaranteedDictionary<Key: Hashable, Value> {
    var storage: [Key: Value]
    var fallback: Value
    subscript(key: Key) -> Value {
        get {
            storage[key] ?? fallback
        }
        set {
            storage[key] = newValue
        }
    }
}

プロパティや subscript に set を併記する場合、値を返せるのは get だけなので、暗黙の return が影響するのも get のみです。

失敗可能イニシャライザでは、nil を返すことが唯一の合法な「値の return」なので、次のような書き方もできます。

class Derived: Base {
    required init?() { nil }
}

暗黙の return が挿入されない例外

本体が単一の式でも、次の2つの場合には暗黙の return は入りません。

  • 関数の戻り値型が Void のとき。たとえば次のコードは、logAndReturnString を返す関数であっても fooVoid を返す関数なので、暗黙の return を入れると型エラーになってしまいます。従来通りのソース互換性を保つため、このケースでは何も返されません。

    func foo() {
        logAndReturn("foo was called")
    }
    
    @discardableResult
    func logAndReturn(_ string: String) -> String { ... }
    
  • 式の型が Never などの uninhabited な型のとき。fatalError() のように絶対に戻ってこない式を本体に書く慣用表現を壊さないための例外です。

    func vendAnInteger() -> Int {
        fatalError()
    }
    

    このコードは従来どおりコンパイルが通り、実行時には fatalError() でプログラムが停止します。

ソース互換性に関する注意点

ごく限られたケースで、既存のコードの意味が変わります。次のようなオーバーロードがある場合、

func bad(value: Int = 0) -> Int { return 0 }
func bad() -> Never { return fatalError() }

func callBad() -> Int { bad() }

これまでは callBad 内の bad()() -> Never を返す 2 番目のオーバーロードに解決されていましたが、暗黙の return 導入後は -> Int を返す 1 番目のオーバーロードに解決されます。「同じ名前のオーバーロードのうち一つだけが Never を返す」構成は実運用ではほとんど現れないため影響は小さいと判断され、そのまま受け入れられました。

ユーザー側のメンタルモデルとしては、「単一の式からなる本体では return を書いても書かなくても同じ意味になる」と捉えるのが正しく、例外的なこのオーバーロード解決の挙動もそのモデルに沿うよう揃えられています。