0

Why can't I modify an initialized value-type immediately after its initialization?!

// using swift 5.5
struct Foo {
    var x: Int = 0
    mutating func add(_ y: Int) -> Foo {
        x += y
        return self
    }
}

var thisFails = Foo().add(42)   // ERROR! "Cannot mutate member on immutable value"
var alsoFails = (Foo().add(42)) // Same error

// test 1:
var test1 = Foo()
test1 = test1.add(13)   // Modifying 'test1' and assigning 'test1' is OKAY

// test 2:
_ = Foo()   // Creating something not assigned to variable is OKAY (but pointless)

// test 3:
extension Foo: CustomStringConvertible {
    var description: String { "Weird \(x)" }
}
print("Test3: \(Foo())") // Creating something not assigned to variable is OKAY

Why is the statement Foo().add(42) problematic?

  1. Save some space on the stack (for a Foo) and initialize it (like is in test#3);
  2. Use it for function add(_);
  3. Take the returned value from add(_) and assign to variable;

Unless I've missed something from Swift's Initialization documentation, I don't understand why this fails.

  • Why don't you simply add the proper initializer? That's what they are meant for. `let foo = Foo(x: 42)`. Btw why would you need to return anything if your intent is to mutate at first place? – Leo Dabus Oct 07 '21 at 23:21

1 Answers1

0

It's because Foo is a struct and a struct is a value type. This means that you cannot really mutate a Foo in place; you have to replace one Foo with another. When you call add on a Foo, you actually get a different Foo (with a different x) and you replace the old one with the new one. This allows you to do that:

var test1 = Foo()
test1 = test1.add(13) // okay too
test1.add(14) // okay too

That's fine, because all along we have a shoebox, test1, and we can assign into it (var), which is what is required in order to mutate a struct. test1 holds a Foo, and when we "mutate" it by saying add, we throw away that Foo and put a different Foo in its place.

Its place.

But Foo().add(13) gives us nothing to assign into so you can't say it. It's incoherent. There is no "place" to put the new Foo.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Even by “throwing away” the old Foo and replacing it. If `Foo()` gives a Foo, and `add()` produces a completely new Foo…. Then, with that logic, ‘Foo().add()` should work. Why bother assigning ‘Foo()’ to a shoebox if we know we’ll be replacing it with a different Foo by the following call? –  Oct 08 '21 at 00:21
  • Simply by existing, Foo exists somewhere in memory, whether it has a named shoebox or not. So I don't see why assigning it a name before or after a mutation changes anything... after all, we can use no-name shoeboxes (like my print example). –  Oct 08 '21 at 00:31
  • I am not here to debate the metaphysics of how _some_ computer language _might_ work, but to help you think productively about the _reality_ of how the Swift language _does_ work. – matt Oct 08 '21 at 00:55