🕊

[Swift] struct 生成時にイニシャライザのデフォルト引数と変数の初期値のどちらが優先されるか?

2022/10/24に公開約4,500字

問題

struct 生成時にイニシャライザのデフォルト引数と変数の初期値のどちらが優先されるか?

答え

デフォルト引数の値が優先される

以下、検証結果です

(1) イニシャライザのデフォルト引数なし、初期値なしの場合

イニシャライザのデフォルト引数なし、初期値なしの場合↓

struct Hoge {
    let moge: Int
    let fuga: Int
}

let hoge: Hoge = .init(moge: 1, fuga: 2)
print(hoge) // Hoge(moge: 1, fuga: 2)

struct は以下のイニシャライザを自動補完してくれます。

struct Hoge {
    let moge: Int
    let fuga: Int

    // 以下のイニシャライザは自動で補完される
    init(moge: Int, fuga: Int) {
        self.moge = moge
        self.fuga = fuga
    }
}

ちなみに class はイニシャライザを自動補完してくれません。

(2) 初期値を設定した場合(let編)

初期値を設定した場合(let編)↓

struct Hoge {
    let moge: Int = 0
    let fuga: Int = 0
}

let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 0, fuga: 0)

注意すべきなのが、以下の記述はできません。

let hoge: Hoge = .init(moge: 1, fuga: 2) // ❌ Argument passed to call that takes no arguments

初期値を与えるとイニシャライザを自動補完してくれないのか、、、と思って、以下のように書いてみたところそんな単純なことではありませんでした。

struct Hoge {
    let moge: Int = 0
    let fuga: Int = 0

    init(moge: Int, fuga: Int) {
        self.moge = moge // ❌ Immutable value 'self.moge' may only be initialized once
        self.fuga = fuga // ❌ Immutable value 'self.fuga' may only be initialized once
    }
}

Immutable value 'self.moge' may only be initialized once

なるほど、let で初期値を宣言すると、イニシャライザを書くことができないみたいです。(知らなかった、、、)

(3) 初期値を設定した場合(var編)

初期値を設定した場合(var編)↓

struct Hoge {
    var moge: Int = 0
    var fuga: Int = 0
}

let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 0, fuga: 0)

ここまでは let で宣言した時と同様の挙動です。

続いて、イニシャライザに引数を与えてみます。

let hoge: Hoge = .init(moge: 1, fuga: 2)
print(hoge) // Hoge(moge: 1, fuga: 2)

ちゃんと動きました。やはりイニシャライザを自動補完してくれるようです。

もちろん以下のように書いても問題ありません。

struct Hoge {
    var moge: Int = 0
    var fuga: Int = 0

    // 初期値を設定しても let のときは怒られたイニシャライザを書くことができる
    init(moge: Int, fuga: Int) {
        self.moge = moge
        self.fuga = fuga
    }
}

ただし、イニシャライザで指定した値で更新されるため、初期値の定義は意味をなしません。
なので、意味のない記述(のはず)なので Xcode が怒ってくれた方がありがたいです。

(4) イニシャライザのデフォルト引数を設定した場合(let編)

イニシャライザのデフォルト引数を設定した場合(let編)↓

struct Hoge {
    let moge: Int
    let fuga: Int

    init(moge: Int = 1, fuga: Int = 2) {
        self.moge = moge
        self.fuga = fuga
    }
}

// 初期値を省略可能
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)

// もちろん、自由な引数をあたえることができます。
let hoge2: Hoge = .init(moge: 3, fuga: 4)
print(hoge2) // Hoge(moge: 3, fuga: 4)

当たり前の挙動ですね。

(5) イニシャライザのデフォルト引数と初期値の両方を設定した場合(let編)

イニシャライザのデフォルト引数と初期値の両方を設定した場合(let編)↓

struct Hoge {
    let moge: Int = 0
    let fuga: Int = 0

    init(moge: Int = 1, fuga: Int = 2) {
        self.moge = moge // ❌ Immutable value 'self.moge' may only be initialized once
        self.fuga = fuga // ❌ Immutable value 'self.fuga' may only be initialized once
    }
}

初期値を設定した場合(let編) でも書いたようにそもそもイニシャライザを設定できません。

(6) イニシャライザのデフォルト引数を設定した場合(var編)

イニシャライザのデフォルト引数を設定した場合(var編)↓

struct Hoge {
    var moge: Int
    var fuga: Int

    init(moge: Int = 1, fuga: Int = 2) {
        self.moge = moge
        self.fuga = fuga
    }
}

// 初期値を省略可能
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)

// もちろん、自由な引数をあたえることができます。
let hoge2: Hoge = .init(moge: 3, fuga: 4)
print(hoge2) // Hoge(moge: 3, fuga: 4)

当たり前の挙動ですね。

(7) 【本題】イニシャライザのデフォルト引数と初期値の両方を設定した場合(var編)

イニシャライザのデフォルト引数と初期値の両方を設定した場合(var編)↓

struct Hoge {
    var moge: Int = 0
    var fuga: Int = 0

    init(moge: Int = 1, fuga: Int = 2) {
        self.moge = moge
        self.fuga = fuga
    }
}

let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)

このようにイニシャライザのデフォルト値が優先されるみたいですね。

もちろんイニシャライザの引数で指定すればそれが上書きされます。

let hoge: Hoge = .init(moge: 3, fuga: 4)
print(hoge) // Hoge(moge: 3, fuga: 4)

となると、イニシャライザのデフォルト引数を設定した場合は、変数の初期値の記述は意味がないみたいですね。

そうなら、Xcode 側でその記述が意味ないことを教えてほしいですね。(本当に意味がないかはわかりませんが、、、)

補足

『イニシャライザのデフォルト値が優先されるということは、実は変数の初期値が先に代入されてイニシャライザの値で再代入されているのでは?』

と思ったので、以下のように didSet 用意して、実験してみました。

struct Hoge {
    var moge: Int = 0 {
        didSet {
            print("oldValue: \(oldValue)")
            print("moge: \(moge)")
        }
    }
    var fuga: Int = 0 {
        didSet {
            print("oldValue: \(oldValue)")
            print("fuga: \(fuga)")
        }
    }

    init(moge: Int = 1, fuga: Int = 2) {
        self.moge = moge
        self.fuga = fuga
    }
}

var hoge: Hoge = .init() // 出力なし

しかしながら、上記のように didSet は走らなかったので、そういうわけではなさそうです。

以上になります。

GitHubで編集を提案

Discussion

ログインするとコメントできます