import Foundationstruct Foo : Printable {private var x: Int = 0mutating func add(n: Int) {x += n}var description: String {return "Foo(\(x))"}}struct Container {var foo = Foo() {didSet {println("didSet foo! was \(oldValue) now \(foo)")}}}
var c = Container()println(c.foo)c.foo.add(10)println(c.foo)
Foo(0)didSet foo! was Foo(0) now Foo(10)Foo(10)
On Jun 2, 2015, at 3:11 PM, Jens Alfke <je...@mooseyard.com> wrote:I’ve run into something unexpected: if I add a didSet block to the definition of a struct property, it’s called when a mutating method is called on the current property value, not just when a new value is assigned. In other words, “c.foo.doSomething()”, where doSomething() is a mutating method, will invoke a didSet block for the property c.foo. There’s no mention of this in the Swift book, as far as I can tell.
Here’s an example:import Foundationstruct Foo : Printable {private var x: Int = 0mutating func add(n: Int) {x += n}var description: String {return "Foo(\(x))"}}struct Container {var foo = Foo() {didSet {println("didSet foo! was \(oldValue) now \(foo)")}}}var c = Container()println(c.foo)c.foo.add(10)println(c.foo)The output when run is:Foo(0)didSet foo! was Foo(0) now Foo(10)Foo(10)
The weirdest part is that the didSet block has access to the struct in both its old and new state. In other words, it looks like the struct is copied before the mutating method is called, so that the didSet block can be passed the copy as its ‘oldValue’ parameter. Now I’m curious whether this behavior happens only if a didSet (or willSet) block is present, or if structs are always mutated this way. It could get expensive to make copies all the time, especially if the struct has references to class objects whose refcounts need to be adjusted.—Jens
--
You received this message because you are subscribed to the Google Groups "Swift Language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to swift-languag...@googlegroups.com.
To post to this group, send email to swift-l...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/02D3D5F8-8A61-48BA-B145-2F0B748E99B5%40mooseyard.com.
For more options, visit https://groups.google.com/d/optout.
On Jun 2, 2015, at 5:05 PM, Chris Lattner <clat...@apple.com> wrote:This is an intentional, and very important part of the model. I explore this area in a bit more detail here:
On Jun 3, 2015, at 3:53 PM, Chris Lattner <clat...@apple.com> wrote:The inout copy in the callee is almost always eliminated. The only place is remains is when captured by a (non-@noescape) closure that isn’t otherwise optimized away (e.g. by being inlined).
var global = 0
func foo(inout x: Int) -> (Int, Int, Int) {
func inc() -> Int {
return ++x
}
return (global, inc(), global)
}
foo(&global) // (0, 1, 1)
global // 1
The result of foo(&global) is (0, 1, 1), which indicates that this is, in fact, call by reference, because ++x mutates both x and global simultaneously. Motivated by your post, I inserted an assignment into foo which stores inc in global variable f:
var global = 0
var f = { 0 }
func foo(inout x: Int) -> (Int, Int, Int) {
func inc() -> Int {
return ++x
}
f = inc
return (global, inc(), global)
}
foo(&global) // (0, 1, 0)
global // 1
It was a total surprise for me to see that just by adding an innocent assignment, which shouldn't impact the computation at all, the result of the function call foo(&global) changed to (0, 1, 0). So, here, the "inout" parameter has real "inout semantics" in that it assigns the result of ++x to global only when the function returns to the client.
Am I right in concluding that the Swift compiler currently incorrectly optimizes cases where the inout parameter isn't captured by a closure that escapes the scope by simply using call by reference?
== Matthias