While reading @Douglas_Gregor's Swift for C++ Practitioners, I came across an example where self was being assigned to in a struct, which prompted me to ask myself the following question.
What is actually stored in a variable that contains a struct value?
Here is a simple example that demonstrates this:
struct Foo {
private var u: Int;
init (u: Int) {
self.u = u
}
}
extension Foo {
var bar: Int {
get {
self.u
}
set {
self = .init (u: newValue)
}
}
}
var p = Foo (u: 2)
print (p)
// Foo (u: 2)
p.bar = 3
print (p)
// Foo (u: 3)
The above program creates an instance of Foo, and then changes the value of that instance's only property bar.
var p = Foo (u: 2)
Q: What is stored in the variable p?
p.bar = 3
The setter changes the value of self, by assigning a new instance to it.
Q: How does this new value of self end up in the variable p?
Interestingly enough, Swift does not allow assignment to self in a class instance.
class Foo {
private var u: Int;
init (u: Int) {
self.u = u
}
}
extension Foo {
var bar: Int {
get {
self.u
}
set {
self = .init (u: newValue) // Cannot assign to value: 'self' is immutable
}
}
}
Setters on structs are mutating by default. Mutating methods pass self inout.
This program is effectively the exact same as the one you wrote:
struct Foo {
private var u: Int
init(u: Int) {
self.u = u
}
static func set(_ `self`: inout Self, _ newValue: Int) {
self = .init(u: newValue)
}
}
do {
var foo = Foo(u: 2)
print(foo)
Foo.set(&foo, 3)
print(foo)
}
Class methods aren't allowed to be mutating because it's usually not what you want for a reference type, and it's potentially confusing. It's entirely artificial, and if you implement a protocol with a default implementation of a mutating method, it will be able to reassign self.
2 Likes
Thank you.
It is interesting that declaring a mutating member func in a class is not allowed, but it is allowed in a protocol.
protocol AssignableSelf {
init (_ u: Int)
mutating func assign (_: Int)
}
extension AssignableSelf {
mutating func assign (_ u: Int) {
self = Self.init (u)
}
}
class Foo: AssignableSelf {
private var u: Int;
required init (_ u: Int) {
self.u = u
}
}
extension Foo: CustomStringConvertible {
var description: String {
"\(type (of: self)): \(self.u)"
}
}
#if false
extension Foo {
mutating func assign (_ u: Int) { // 'mutating' is not valid on instance methods in classes
self = Self.init (u) // Cannot assign to value: 'self' is immutable
}
}
#endif
do {
var p = Foo (2)
print (p)
let q = p
p.assign (4)
print (p)
assert (!(p === q))
}
tera
4
BTW, isn't this a miss-feature? I understand it could be potentially useful for something (now that we have it), but it does look a hack and I wonder does it pass the "if we didn't have this feature already would we introduce it now" test.