What's in a struct variable?

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))
}

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.