MENU

【Swift初心者向け】mutatingキーワードとは?構造体を変更する方法を完全解説

目次

Swiftのmutatingって何?基本を理解しよう

Swift プログラミングで出てくる mutating キーワード。初心者の方には「なぜ必要なの?」と疑問に思うかもしれません。

一言で言うと 「構造体(struct)のメソッドが、自分自身のプロパティを変更する時に必要なキーワード」です。

mutating は「変更する」「変異させる」という意味の英語で、Swiftでは構造体が自分自身を変更することを明示的に示すために使われます。

なぜmutatingが必要なの?

まず、なぜSwiftにこのようなキーワードが必要なのか、理解しましょう。

Swiftの値型と参照型

Swiftには2種類のデータ型があります。

値型(Value Type)

  • 構造体(struct)
  • 列挙型(enum)
  • コピーが作られる

参照型(Reference Type)

  • クラス(class)
  • メモリ上の同じ場所を参照する

構造体の特徴:デフォルトで変更不可

Swiftの構造体は、安全性を保つためにデフォルトでプロパティを変更できません

struct Point {
    var x: Int
    var y: Int
    
    // エラー!構造体のメソッドはデフォルトで自身を変更できない
    func moveRight() {
        x += 1  // ❌ コンパイルエラー
    }
}

このエラーが出るのは、Swiftが「意図しない変更」を防ぐためです。

mutatingで変更を明示する

mutating キーワードを付けることで、「このメソッドは自分自身を変更します」と明示的に宣言できます。

struct Point {
    var x: Int
    var y: Int
    
    // ✅ mutatingを付ければOK
    mutating func moveRight() {
        x += 1  // 変更できる
    }
}

日常生活で理解するmutating

例1:ノートに書き込む

mutating(変更する)

  • 既存のノートに直接書き込む
  • ノートそのものが変わる
  • ノートは同じだが、内容が変わる

non-mutating(変更しない)

  • 元のノートはそのまま
  • 新しいページやノートに書く
  • 元のノートは変わらない

例2:銀行口座の残高

mutating(変更する)

// 口座残高を直接変更
口座.残高 = 口座.残高 + 1000円

non-mutating(変更しない)

// 新しい口座情報を作成
新しい口座 = 口座をコピーして残高を1000円増やしたもの

Swiftの構造体では、どちらの動作をするのか明確にする必要があります。それが mutating キーワードの役割です。

mutatingの基本的な使い方

例1:座標を表す構造体

struct Point {
    var x: Int
    var y: Int
    
    // mutatingメソッド:自分自身を変更
    mutating func move(x deltaX: Int, y deltaY: Int) {
        x += deltaX
        y += deltaY
    }
    
    // non-mutatingメソッド:新しいインスタンスを返す
    func moved(x deltaX: Int, y deltaY: Int) -> Point {
        return Point(x: x + deltaX, y: y + deltaY)
    }
}

// 使い方
var point = Point(x: 0, y: 0)

// mutatingメソッドを使用
point.move(x: 5, y: 10)
print(point)  // Point(x: 5, y: 10) - 元の値が変わる

// non-mutatingメソッドを使用
let newPoint = point.moved(x: 3, y: 3)
print(point)     // Point(x: 5, y: 10) - 元は変わらない
print(newPoint)  // Point(x: 8, y: 13) - 新しいインスタンス

例2:銀行口座の構造体

struct BankAccount {
    var balance: Double
    var accountNumber: String
    
    // mutatingメソッド:残高を直接変更
    mutating func deposit(amount: Double) {
        if amount > 0 {
            balance += amount
            print("入金成功: \(amount)円")
        }
    }
    
    mutating func withdraw(amount: Double) -> Bool {
        if amount > 0 && balance >= amount {
            balance -= amount
            print("出金成功: \(amount)円")
            return true
        }
        print("出金失敗: 残高不足")
        return false
    }
    
    // non-mutatingメソッド:情報を取得するだけ
    func getBalance() -> Double {
        return balance
    }
    
    func displayInfo() {
        print("口座番号: \(accountNumber)")
        print("残高: \(balance)円")
    }
}

// 使い方
var account = BankAccount(balance: 10000, accountNumber: "123-456")

account.deposit(amount: 5000)    // 入金成功: 5000円
print(account.balance)           // 15000.0

account.withdraw(amount: 3000)   // 出金成功: 3000円
print(account.balance)           // 12000.0

account.displayInfo()
// 出力:
// 口座番号: 123-456
// 残高: 12000.0円

varとletの違いとmutating

mutatingメソッドを呼び出すには、インスタンスが var で宣言されている必要があります。

var で宣言(変更可能)

var point = Point(x: 0, y: 0)
point.move(x: 5, y: 10)  // ✅ OK - varなので変更できる
print(point)  // Point(x: 5, y: 10)

let で宣言(変更不可)

let point = Point(x: 0, y: 0)
point.move(x: 5, y: 10)  // ❌ エラー!letは変更できない

エラーメッセージ

error: cannot use mutating member on immutable value: 'point' is a 'let' constant

理由

  • let は定数を意味する
  • 定数は変更できない
  • mutatingメソッドは変更を行うため、let では呼び出せない

non-mutatingメソッドはletでもOK

let point = Point(x: 0, y: 0)
let newPoint = point.moved(x: 5, y: 10)  // ✅ OK
print(point)     // Point(x: 0, y: 0) - 元は変わらない
print(newPoint)  // Point(x: 5, y: 10)

mutatingが必要な場面

場面1:プロパティの値を変更する

struct Counter {
    var count: Int = 0
    
    // countを変更するのでmutatingが必要
    mutating func increment() {
        count += 1
    }
    
    mutating func decrement() {
        count -= 1
    }
    
    mutating func reset() {
        count = 0
    }
}

var counter = Counter()
counter.increment()  // count = 1
counter.increment()  // count = 2
counter.decrement()  // count = 1
counter.reset()      // count = 0

場面2:複数のプロパティを変更する

struct Rectangle {
    var width: Double
    var height: Double
    
    // 複数のプロパティを変更
    mutating func scale(by factor: Double) {
        width *= factor
        height *= factor
    }
    
    // 面積の計算(変更しないのでmutating不要)
    func area() -> Double {
        return width * height
    }
}

var rect = Rectangle(width: 10, height: 20)
print(rect.area())  // 200.0

rect.scale(by: 2.0)
print(rect)         // Rectangle(width: 20.0, height: 40.0)
print(rect.area())  // 800.0

場面3:自分自身を完全に置き換える

struct Player {
    var name: String
    var score: Int
    var level: Int
    
    // 自分自身を新しいインスタンスで置き換える
    mutating func levelUp() {
        self = Player(name: name, score: score, level: level + 1)
        print("\(name)がレベルアップ!レベル\(level)になりました")
    }
    
    mutating func addScore(_ points: Int) {
        score += points
        
        // 1000点ごとにレベルアップ
        if score >= level * 1000 {
            levelUp()
        }
    }
}

var player = Player(name: "太郎", score: 0, level: 1)

player.addScore(500)   // score = 500
player.addScore(600)   // score = 1100, レベルアップ!
// 出力: 太郎がレベルアップ!レベル2になりました

mutatingが不要な場面

場面1:プロパティを読み取るだけ

struct Book {
    var title: String
    var author: String
    var pages: Int
    
    // 読み取るだけなのでmutating不要
    func getInfo() -> String {
        return "\(title) by \(author) (\(pages)ページ)"
    }
    
    func isLongBook() -> Bool {
        return pages > 300
    }
}

let book = Book(title: "Swiftの本", author: "山田太郎", pages: 450)
print(book.getInfo())      // Swiftの本 by 山田太郎 (450ページ)
print(book.isLongBook())   // true

場面2:計算結果を返すだけ

struct Circle {
    var radius: Double
    
    // 計算するだけ(変更しない)
    func area() -> Double {
        return Double.pi * radius * radius
    }
    
    func circumference() -> Double {
        return 2 * Double.pi * radius
    }
    
    func diameter() -> Double {
        return radius * 2
    }
}

let circle = Circle(radius: 5.0)
print(circle.area())           // 78.53981633974483
print(circle.circumference())  // 31.41592653589793
print(circle.diameter())       // 10.0

クラスではmutatingが不要な理由

クラスは参照型なので、mutating キーワードは必要ありません。

class PointClass {
    var x: Int
    var y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
    
    // mutating不要!クラスのメソッドは自由にプロパティを変更できる
    func move(x deltaX: Int, y deltaY: Int) {
        x += deltaX
        y += deltaY
    }
}

let pointClass = PointClass(x: 0, y: 0)
pointClass.move(x: 5, y: 10)  // letでも変更できる
print(pointClass.x, pointClass.y)  // 5 10

なぜletでも変更できる?

  • クラスは参照型
  • let は「参照先を変更できない」という意味
  • 参照先のオブジェクトの中身は変更できる

structとclassの比較

特徴struct(構造体)class(クラス)
値型参照型
mutating必要不要
let での変更不可可能(中身のみ)
コピー完全なコピー参照のコピー
継承不可可能
// 構造体の例
struct StructPoint {
    var x: Int
    var y: Int
    
    mutating func moveRight() {
        x += 1
    }
}

var sp1 = StructPoint(x: 0, y: 0)
var sp2 = sp1  // 完全なコピーが作られる
sp2.moveRight()
print(sp1.x)  // 0 - sp1は変わらない
print(sp2.x)  // 1

// クラスの例
class ClassPoint {
    var x: Int
    var y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
    
    func moveRight() {
        x += 1
    }
}

let cp1 = ClassPoint(x: 0, y: 0)
let cp2 = cp1  // 参照のコピー(同じオブジェクトを指す)
cp2.moveRight()
print(cp1.x)  // 1 - cp1も変わる
print(cp2.x)  // 1

実践的なmutatingの使用例

例1:ショッピングカート

struct ShoppingCart {
    private var items: [Item] = []
    private var discountRate: Double = 0.0
    
    struct Item {
        let name: String
        let price: Double
        var quantity: Int
    }
    
    // 商品を追加(mutating)
    mutating func addItem(name: String, price: Double, quantity: Int) {
        if let index = items.firstIndex(where: { $0.name == name }) {
            // 既にある商品なら数量を増やす
            items[index].quantity += quantity
        } else {
            // 新しい商品を追加
            items.append(Item(name: name, price: price, quantity: quantity))
        }
        print("\(name) x \(quantity)を追加しました")
    }
    
    // 商品を削除(mutating)
    mutating func removeItem(name: String) {
        items.removeAll { $0.name == name }
        print("\(name)を削除しました")
    }
    
    // 割引を適用(mutating)
    mutating func applyDiscount(_ rate: Double) {
        if rate >= 0 && rate <= 1 {
            discountRate = rate
            print("\(Int(rate * 100))%の割引を適用しました")
        }
    }
    
    // カートをクリア(mutating)
    mutating func clear() {
        items.removeAll()
        discountRate = 0.0
        print("カートを空にしました")
    }
    
    // 合計金額を計算(non-mutating)
    func totalPrice() -> Double {
        let subtotal = items.reduce(0.0) { $0 + ($1.price * Double($1.quantity)) }
        return subtotal * (1.0 - discountRate)
    }
    
    // カートの内容を表示(non-mutating)
    func displayItems() {
        print("=== カートの内容 ===")
        for item in items {
            print("\(item.name): \(item.price)円 x \(item.quantity)")
        }
        if discountRate > 0 {
            print("割引: \(Int(discountRate * 100))%")
        }
        print("合計: \(totalPrice())円")
        print("==================")
    }
}

// 使い方
var cart = ShoppingCart()

cart.addItem(name: "リンゴ", price: 150, quantity: 3)
// 出力: リンゴ x 3を追加しました

cart.addItem(name: "バナナ", price: 100, quantity: 5)
// 出力: バナナ x 5を追加しました

cart.applyDiscount(0.1)
// 出力: 10%の割引を適用しました

cart.displayItems()
// 出力:
// === カートの内容 ===
// リンゴ: 150.0円 x 3
// バナナ: 100.0円 x 5
// 割引: 10%
// 合計: 855.0円
// ==================

cart.removeItem(name: "リンゴ")
// 出力: リンゴを削除しました

cart.displayItems()

例2:タイマー機能

struct Timer {
    private var seconds: Int = 0
    private var isRunning: Bool = false
    
    // タイマーを開始(mutating)
    mutating func start() {
        if !isRunning {
            isRunning = true
            print("タイマーを開始しました")
        } else {
            print("タイマーは既に動作中です")
        }
    }
    
    // タイマーを停止(mutating)
    mutating func stop() {
        if isRunning {
            isRunning = false
            print("タイマーを停止しました")
        } else {
            print("タイマーは停止しています")
        }
    }
    
    // 1秒経過(mutating)
    mutating func tick() {
        if isRunning {
            seconds += 1
        }
    }
    
    // リセット(mutating)
    mutating func reset() {
        seconds = 0
        isRunning = false
        print("タイマーをリセットしました")
    }
    
    // 時間を取得(non-mutating)
    func getFormattedTime() -> String {
        let hours = seconds / 3600
        let minutes = (seconds % 3600) / 60
        let secs = seconds % 60
        return String(format: "%02d:%02d:%02d", hours, minutes, secs)
    }
    
    // 秒数を取得(non-mutating)
    func getSeconds() -> Int {
        return seconds
    }
    
    // 動作状態を確認(non-mutating)
    func running() -> Bool {
        return isRunning
    }
}

// 使い方
var timer = Timer()

timer.start()
// 出力: タイマーを開始しました

// 5秒経過をシミュレート
for _ in 1...5 {
    timer.tick()
}

print(timer.getFormattedTime())
// 出力: 00:00:05

timer.stop()
// 出力: タイマーを停止しました

timer.reset()
// 出力: タイマーをリセットしました

例3:ゲームのキャラクター

struct Character {
    var name: String
    private var health: Int
    private var maxHealth: Int
    private var attackPower: Int
    private var level: Int
    
    init(name: String) {
        self.name = name
        self.health = 100
        self.maxHealth = 100
        self.attackPower = 10
        self.level = 1
    }
    
    // ダメージを受ける(mutating)
    mutating func takeDamage(_ damage: Int) {
        health -= damage
        if health < 0 {
            health = 0
        }
        print("\(name)は\(damage)のダメージを受けた!残りHP: \(health)")
        
        if health == 0 {
            print("\(name)は倒れた...")
        }
    }
    
    // 回復する(mutating)
    mutating func heal(_ amount: Int) {
        health += amount
        if health > maxHealth {
            health = maxHealth
        }
        print("\(name)は\(amount)回復した!HP: \(health)")
    }
    
    // レベルアップ(mutating)
    mutating func levelUp() {
        level += 1
        maxHealth += 20
        health = maxHealth
        attackPower += 5
        print("\(name)はレベルアップした!レベル\(level)!")
        print("最大HP: \(maxHealth), 攻撃力: \(attackPower)")
    }
    
    // 攻撃する(non-mutating、相手にダメージを与える)
    func attack(_ target: inout Character) {
        print("\(name)の攻撃!")
        target.takeDamage(attackPower)
    }
    
    // ステータス表示(non-mutating)
    func displayStatus() {
        print("=== \(name) ===")
        print("レベル: \(level)")
        print("HP: \(health)/\(maxHealth)")
        print("攻撃力: \(attackPower)")
        print("==============")
    }
    
    // 生存確認(non-mutating)
    func isAlive() -> Bool {
        return health > 0
    }
}

// 使い方
var hero = Character(name: "勇者")
var monster = Character(name: "スライム")

hero.displayStatus()
// 出力:
// === 勇者 ===
// レベル: 1
// HP: 100/100
// 攻撃力: 10
// ==============

hero.attack(&monster)
// 出力:
// 勇者の攻撃!
// スライムは10のダメージを受けた!残りHP: 90

monster.attack(&hero)
// 出力:
// スライムの攻撃!
// 勇者は10のダメージを受けた!残りHP: 90

hero.heal(20)
// 出力: 勇者は20回復した!HP: 100

hero.levelUp()
// 出力:
// 勇者はレベルアップした!レベル2!
// 最大HP: 120, 攻撃力: 15

mutatingを使う際の注意点

注意点1:letでは呼び出せない

let point = Point(x: 0, y: 0)
// point.move(x: 5, y: 10)  // ❌ エラー

// 解決策1: varで宣言する
var mutablePoint = Point(x: 0, y: 0)
mutablePoint.move(x: 5, y: 10)  // ✅ OK

// 解決策2: non-mutatingメソッドを使う
let newPoint = point.moved(x: 5, y: 10)  // ✅ OK

注意点2:計算プロパティのsetterはmutating

struct Temperature {
    var celsius: Double
    
    var fahrenheit: Double {
        get {
            return celsius * 9 / 5 + 32
        }
        // setterは暗黙的にmutating
        set {
            celsius = (newValue - 32) * 5 / 9
        }
    }
}

var temp = Temperature(celsius: 0)
print(temp.fahrenheit)  // 32.0

temp.fahrenheit = 100  // setterを呼び出す(mutating)
print(temp.celsius)    // 37.77...

注意点3:配列や辞書のメソッドもmutating

struct TodoList {
    var items: [String] = []
    
    // 配列を変更するのでmutating
    mutating func add(_ item: String) {
        items.append(item)  // append()はmutating
    }
    
    mutating func remove(at index: Int) {
        items.remove(at: index)  // remove()はmutating
    }
    
    mutating func clear() {
        items.removeAll()  // removeAll()はmutating
    }
}

よくある質問(FAQ)

Q1:いつmutatingを付けるべきですか?

A:プロパティを変更する時は必ずmutatingを付けます

mutatingが必要

  • プロパティの値を変更する
  • 配列や辞書に要素を追加・削除する
  • self を置き換える

mutatingが不要

  • プロパティを読み取るだけ
  • 計算して結果を返すだけ
  • 新しいインスタンスを作って返す

Q2:クラスでもmutatingは使えますか?

A:いいえ、クラスではmutatingは使えません(そして不要です)

class MyClass {
    var value: Int = 0
    
    // mutatingは付けられない(エラーになる)
    func increment() {
        value += 1  // クラスなら自由に変更できる
    }
}

Q3:mutatingメソッドの中でnon-mutatingメソッドを呼べますか?

A:はい、呼べます

struct Counter {
    var count: Int = 0
    
    func getCurrentCount() -> Int {
        return count
    }
    
    mutating func incrementAndPrint() {
        count += 1
        // non-mutatingメソッドを呼び出せる
        print("現在のカウント: \(getCurrentCount())")
    }
}

Q4:プロトコルでmutatingを指定できますか?

A:はい、できます

protocol Incrementable {
    mutating func increment()
}

struct Counter: Incrementable {
    var value: Int = 0
    
    mutating func increment() {
        value += 1
    }
}

class CounterClass: Incrementable {
    var value: Int = 0
    
    // クラスの場合はmutatingを付けない
    func increment() {
        value += 1
    }
}

Q5:inout パラメータとmutatingの違いは?

A:用途が異なります

  • mutating:自分自身(self)を変更する
  • inout:引数として渡された外部の値を変更する
struct Point {
    var x: Int
    var y: Int
    
    // mutating: 自分自身を変更
    mutating func move(x deltaX: Int, y deltaY: Int) {
        self.x += deltaX
        self.y += deltaY
    }
    
    // inout: 引数を変更
    func moveOther(_ other: inout Point, x deltaX: Int, y deltaY: Int) {
        other.x += deltaX
        other.y += deltaY
    }
}

まとめ:mutatingキーワードをマスターしよう

Swiftの mutating キーワードは、構造体の安全性を保ちながら変更を可能にする重要な機能です。

重要ポイントまとめ

  • mutating は構造体のメソッドが自身を変更する時に必要
  • クラスでは mutating は不要(参照型のため)
  • var で宣言されたインスタンスでのみ呼び出し可能
  • let で宣言されたインスタンスでは呼び出せない
  • プロパティを読み取るだけなら mutating は不要
  • Swiftの値型の安全性を保つための仕組み

使い分けのガイドライン

動作キーワード使用例
プロパティを変更mutatingmutating func increment()
値を読み取るだけ不要func getValue() -> Int
新しいインスタンスを返す不要func moved() -> Point
クラスのメソッド不要func update()

初心者へのアドバイス

  1. まずは mutating を付けずに書いてみる
  2. コンパイルエラーが出たら mutating を追加
  3. varlet の違いを意識する
  4. 必要に応じて non-mutating 版も用意する
  5. 実際にコードを書いて動作を確認する

Swiftの mutating キーワードを理解することで、より安全で予測可能なコードが書けるようになります。値型と参照型の違いを理解し、適切に mutating を使いこなせるSwiftプログラマーを目指しましょう!

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次