Reputation: 257
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
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
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
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