Tysac
Tysac

Reputation: 257

private(set) raises 'self' is immutable within struct

I don't know if I am just too tired, but I think there is more to it. (I tried two cups of coffee, but still couldn't solve the problem...)

I want to make a variable read-only from the outside, but writable through methods. (So like in that example I can make sure favoriteNumber is only single-digit.

(I don't want to make it a computed property and do that in get {}, set{}, because in my other project I want to modify the variable based on a value different than "newValue" of set {})

struct Person {

    var name: String
    private(set) var favoriteNumber: Int? // Should only be single-digit

    init(name: String, number: Int) {

        self.name = name

        // Make sure favorite number is single-digit
        guard number >= 0 && number < 10 else {
            self.favoriteNumber = nil
        }
        self.favoriteNumber = number
    }

    func changeFavoriteNumber(number: Int) {
        guard number >= 0 && number < 10 else { return }
        self.favoriteNumber = number
    }
}

.

The line

self.favoriteNumber = number 

in the function

changeFavoriteNumber(number:)

raises the error

"Cannot assign to property: 'self' is immutable"

and suggests "Mark method 'mutating' to make 'self' mutable". But that's not what I want, since I don't want to modify an instance of type Person, but a mutable variable... (var favoriteNumber)

Supposed to be used in that way:

let frank = Person.init(name: "Frank", number: 9)
frank.changeFavoriteNumber(number: 8)

I have no idea what's going on here (even after 3 coffees now :)

Upvotes: 9

Views: 3669

Answers (3)

Gihan
Gihan

Reputation: 2536

You should not think structures in the same way you think about classes. Structs in swift have value semantics. You cant change any of the properties of a structure defined using let. Even though the properties inside the structure are defined with var, because origin is defined with let.

example: Now you can do this:

frank.favoriteNumber = 10

But if you want to mutate the properties of a structure, since it referes to value semantics, you have to use mutating with your function. Mutating because you are mutating the properties of a structure. Try this Change :

mutating func changeFavoriteNumber(number: Int) {
        guard number >= 0 && number < 10 else { return }
        self.favoriteNumber = number
    }

var frank = Person.init(name: "Frank", number: 9)
frank.changeFavoriteNumber(number: 8)

From apple documentation:

Modifying Value Types from Within Instance Methods Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods. However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behavior for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends. The method can also assign a completely new instance to its implicit self property, and this new instance will replace the existing one when the method ends. You can opt in to this behavior by placing the mutating keyword before the func keyword for that method: URL

Upvotes: 2

NobodyNada
NobodyNada

Reputation: 7634

Structs are value types, and have different semantics than classes. A variable storing an instance of a class is stored as a reference to the actual object elsewhere in memory; this means that you can have two variables pointing to the same object, and you can modify a class's members whether or not the variables referencing it are mutable.

A struct is different. A variable storing an instance of a struct stores the struct's members directly, instead of as a reference to an object somewhere else. This means that passing a struct to a function or another variable creates a copy of it, and modifying a struct directly modifies the variable storing it.

Therefore, a struct is immutable unless it's stored in a var. Functions that mutate the struct must be declared mutating as the compiler suggests, so that the compiler can enforce that only non-mutating functions are called on let structs.

Upvotes: 6

dr_barto
dr_barto

Reputation: 6065

If you don't want to use mutating then you will have to make Person a class instead of a struct. The latter doesn't allow mutations unless the method is marked mutable and the instance is a var variable instead of a let variable.


For completeness, I'll also mention a way to solve this with a struct, but I advise against using this workaround. You can "box" the property value in another (reference type) object; this way you can disguise the mutation. Something like this:

class Box<T> {
  var value: T
  init(_ value: T) { self.value = value }
}

struct Person {
  private var favoriteNumber: Box<Int> = Box(0)

  func changeFavoriteNumber(_ number: Int) {
    self.favoriteNumber.value = number
  }
}

Now you can access the value inside Person with favoriteNumber.value.

Upvotes: 4

Related Questions