There is something that I don't entirely understand when it comes to mutating value types in Swift.
As the "The Swift Programming Language" iBook states: By default, the properties of a value type cannot be modified from within its instance methods.
And so to make this possible we can declare methods with the mutating
keyword inside structs and enums.
The thing that is not entirely clear to me is this: You can change a var from outside a struct, but you cannot change it from its own methods. This seems counter-intuitive to me, as in Object Oriented languages, you generally try to encapsulate variables so they can only be changed from within. With structs this appears to be the other way around. To elaborate, here's a code snippet:
struct Point {
var x = 0, y = 0
mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
self.x = x
self.y = y
}
}
var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5)
Does anyone know the reason why structs cannot change their content from inside their own context, while the contents can easily be changed elsewhere?
The mutability attribute is marked on a storage (constant or variable), not a type. You can think struct has two modes: mutable and immutable. If you assign a struct value to an immutable storage (we call it let
or constant in Swift) the value becomes immutable mode, and you cannot change any state in the value. (including calling any mutating method)
If the value is assigned to a mutable storage (we call it var
or variable in Swift), you're free to modify the state of them, and calling of mutating method is allowed.
In addition, classes don't have this immutable/mutable mode. IMO, this is because classes are usually used to represent reference-able entity. And reference-able entity is usually mutable because it's very hard to make and manage reference graphs of entities in immutable manner with proper performance. They may add this feature later, but not now at least.
For Objective-C programmers, mutable/immutable concepts are very familiar. In Objective-C we had two separated classes for each concept, but in Swift, you can do this with one struct. Half work.
For C/C++ programmers, this is also very familiar concept. This is exactly what const
keyword do in C/C++.
Also, immutable value can be very nicely optimised. In theory, Swift compiler (or LLVM) can perform copy-elision on values passed by let
, just like in C++. If you use immutable struct wisely, it will outperform refcounted classes.
As @Joseph claimed this doesn't provide why, I am adding a little more.
Structs have two kind of methods. plain and mutating methods. Plain method implies immutable (or non-mutating). This separation exists only to support immutable semantics. An object in immutable mode shouldn't change its state at all.
Then, immutable methods must guarantee this semantic immutability. Which means it shouldn't change any internal value. So compiler disallows any state changes of itself in a immutable method. In contrast, mutating methods are free to modify states.
And then, you may have a question of why immutable is the default? That's because it's very hard to predict the future state of mutating values, and that usually becomes the main source of headaches and bugs. Many people agreed that the solution is avoiding mutable stuffs, and then immutable by default was on top of wish list for decades in C/C++ family languages and its derivations.
See purely functional style for more details. Anyway, we still need mutable stuffs because immutable stuffs have some weaknesses, and discussing about them seems to be out of topic.
I hope this helps.
Caution: layman's terms ahead.
This explanation isn't rigorously correct at the most nitty-gritty code level. However it has been reviewed by a guy who actually works on Swift and he said it's good enough as a basic explanation.
So I want to try to simply and directly answer the question of "why".
To be precise: why do we have to mark struct functions as mutating
when we can change struct parameters without any modifying keywords?
So, big picture, it has a lot to do with the philosophy that keeps Swift swift.
You could kind of think of it like the problem of managing actual physical addresses. When you change your address, if there are a lot of people who have your current one, you have to notify all of them that you've moved. But if no one has your current address, you can just move wherever you want, and no one needs to know.
In this situation, Swift is kind of like the post office. If lots of people with lots of contacts move around a lot, it has a really high overhead. It has to pay a big staff of people to handle all those notifications, and the process takes up a lot of time and effort. That's why Swift's ideal state is for everyone in its town to have as few contacts as possible. Then it doesn't need a big staff for handling address changes, and it can do everything else faster and better.
This is also why Swift-folks are all raving on about value types vs. reference types. By nature, reference types rack up "contacts" all over the place, and value types usually don't need more than a couple. Value types are "Swift"-er.
So back to small picture: structs
. Structs are a big deal in Swift because they can do most of the things objects can do, but they're value types.
Let's continue the physical address analogy by imagining a misterStruct
that lives in someObjectVille
. The analogy gets a little wonked up here, but I think it still is helpful.
So to model changing a variable on a struct
, let's say misterStruct
has green hair, and gets an order to switch to blue hair. The analogy gets wonked, like I said, but sort of what happens is that instead of changing misterStruct
's hair, the old person moves out and a new person with blue hair moves in, and that new person begins calling themselves misterStruct
. No one needs to get a change of address notification, but if anyone looks at that address, they'll see a guy with blue hair.
Now let's model what happens when you call a function on a struct
. In this case, it's like misterStruct
gets an order such as changeYourHairBlue()
. So the post office delivers the instruction to misterStruct
"go change your hair to blue and tell me when you're done."
If he's following the same routine as before, if he's doing what he did when the variable was changed directly, what misterStruct
will do is move out of his own house and call in a new person with blue hair. But that's the problem.
The order was "go change your hair to blue and tell me when you're done," but it's the green guy who got that order. After the blue guy moves in, a "job complete" notification still has to be sent back. But the blue guy knows nothing about it.
[To really wonk up this analogy something awful, what technically happened to green-haired guy was that after he moved out, he immediately committed suicide. So he can't notify anyone that the task is complete either!]
To avoid this problem, in cases like this only, Swift has to go in directly to the house at that address and actually change the current inhabitant's hair. That is a completely different process than just sending in a new guy.
And that's why Swift wants us to use the mutating
keyword!
The end result looks the same to anything that has to refer to the struct: the inhabitant of the house now has blue hair. But the processes for achieving it are actually completely different. It looks like it's doing the same thing, but it's doing a very different thing. It's doing a thing that Swift structs in general never do.
So to give the poor compiler a little help, and not make it have to figure out whether a function mutates the struct
or not, on its own, for every single struct function ever, we are asked to have pity and use the mutating
keyword.
In essence, to help Swift stay swift, we all must do our part. :)
A structure is an aggregation of fields; if a particular structure instance is mutable, its fields will be mutable; if an instance is immutable, its fields will be immutable. A structure type must thus be prepared for the possibility that the fields of any particular instance may be mutable or immutable.
In order for a structure method to mutate the fields of the underlying struct, those fields have to be mutable. If a method that mutates fields of the underlying struct is invoked upon an immutable structure, it would try to mutate immutable fields. Since nothing good could come of that, such invocation needs to be forbidden.
To achieve that, Swift divides structure methods into two categories: those that modify the underlying structure, and thus may only be invoked on mutable structure instances, and those that do not modify the underlying structure and should thus be invokable on both mutable and immutable instances. The latter usage is probably the more frequent, and is thus the default.
By comparison, .NET presently (still!) offers no means of distinguishing structure methods that modify the structure from those that don't. Instead, invoking a structure method on an immutable structure instance will cause the compiler to make a mutable copy of the structure instance, let the method do whatever it wants with it, and discard the copy when the method is done. This has the effect of forcing the compiler to waste time copying the structure whether or not the method modifies it, even though adding the copy operation will almost never turn what would be semantically-incorrect code into semantically-correct code; it will merely cause code that is semantically wrong in one way (modifying an "immutable" value) to be wrong in a different way (allowing code to think it's modifying a structure, but discarding the attempted changes). Allowing struct methods to indicate whether they will modify the underlying structure can eliminate the need for a useless copy operation, and also ensures that attempted erroneous usage will get flagged.
Swift structs can be instantiated as either constants (via let
) or variables (via var
)
Consider Swift's Array
struct (yes it's a struct).
var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]
let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do
Why didn't the append work with the planet names? Because append is marked with the mutating
keyword. And since planetNames
was declared using let
, all methods thus marked are off limits.
In your example the compiler can tell you're modifying the struct by assigning to one or more of its properties outside of an init
. If you change your code a bit you'll see that x
and y
aren't always accessible outside the struct. Notice the let
on the first line.
let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With