Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

didSet in Swift has a weird knock-on effect on mutating func

Tags:

ios

swift

I just learned that mutating func is just a curried func with first parameter as inout, so the code below will work and change firstName to "John"

struct Person {
    var firstName = "Matt"

    mutating func changeName(fn: String) {
        firstName = fn
    }
}
var p = Person() 
let changer = Person.changeName
changer(&p)("John")
p.firstName

but the odd thing happend when I add property observer to p like below, you can see firstName is still "Matt", why? enter image description here

like image 722
dispute Avatar asked Feb 22 '16 14:02

dispute


2 Answers

An interesting note to take ist that the observer is called before the curried setter is called:

struct Person {
    var firstName = "Matt"

    mutating func changeName(fn: String) {
        firstName = fn
    }
}

var p: Person = Person() {
    didSet {
        print("p was set")
    }
}

print("1: \(p.firstName)")
let changer = Person.changeName
print("2: \(p.firstName)")
let setter = changer(&p)
print("3: \(p.firstName)")
setter("John")
print("4: \(p.firstName)")
p.changeName("John")
print("5: \(p.firstName)")

This prints:

1: Matt
2: Matt
p was set
3: Matt
4: Matt
p was set
5: John

So it seems that acquiring the setter method on the inout struct performs the actual mutation. This is explained by how inout parameters work semantically: When the parameter is passed to the function its value is copied to a place where the function can mutate it. When the function returns, the value is copied back to the original place, triggering setter observers once, whether the value did change or not.

When we change the way to get the pre-filled curried setter to:

let setter = p.changeName

... the compiler objects:

error: partial application of 'mutating' method is not allowed

It seems that the compiler understands that closing over an inout value is a bad idea, as it is basically taking a reference on a value type.

The closure would let you change the value of the struct at any time, even when the compiler assumes it to be constant. To prevent this unfortunate situation, the compiler simply forbids closing over the inout.

You found a case which fools the compiler and works around the diagnostic. This seems to be an error and it should be filed.

Short version:

struct Foo {
    mutating func foo() {}
}

var f = Foo()
let m = Foo.foo
let s = m(&f)

One of the last two lines should emit an error, similar to let x = f.foo.

like image 175
Nikolai Ruhe Avatar answered Oct 23 '22 00:10

Nikolai Ruhe


in the newest accepted proposal 0042-flatten-method-type, self is no more passed as curried function, so this problem is solved in the Swift 3

like image 25
dispute Avatar answered Oct 23 '22 01:10

dispute