Not expected result when changing a variable in a "defer" block, if the variable has observers willSet or didSet.

Why is the result 3? Is this the correct behavior?

func foo()
{
  var i = 0 {
    willSet {}
  }
   
  defer {
    i -= 1
    print(i) // expected result - 2, prints - 3
  }
   
  i = 3
}

I changed by adding a statement in willSet:

func foo() {
  var i = 0 {
    willSet { print("willSet", i) }
  }
   
  defer {
    i -= 1
    print("defer", i) // expected result - 2, prints - 3
  }
   
  i = 3
}

foo()

And get the expected result willSet 0 willSet 3 defer 2

I instrumented more:

func foo() {
  var i = 0 {
    willSet { print("willSet, i is still", i) }
  }
   
  defer {
    i -= 1
    print("defer", i) // expected result - 2, prints - 3
  }
    
   print("soon i = 3, now is", i)
  i = 3
    print("now i = 3", i)
}

foo()

And get:

  • soon i = 3, now is 0
  • willSet, i is still 0
  • now i = 3 3
  • willSet, i is still 3
  • defer 2

I remove the call to i in willSet:

    willSet { print("willSet") } // , i is still", i) }

And get:

  • soon i = 3, now is 0
  • willSet
  • now i = 3 3
  • willSet
  • defer 3

Thanks for finding and sharing an interesting example.

For me, it seems to be a bug of Swift. You should better send a bug report to swift.org.

Effectively, exact same problem with didSet.

I tracked in debugger and I observe that willSet does not update i:

func foo() {
  var i = 0 {
     willSet { print("willSet") }
  }
   
  defer {
    i -= 1
    print("defer", i) // expected result - 2, prints - 3
  }
    
   print("soon i = 3, now is", i)
  i = 3
    print("now i = 3", i)
}

foo()

Here is the sequence and values in console:

Statement                     value in console

  • var i = 0
  • print("Soon")             i = 0
  • i = 3                            value = 3
  • willSet                        newValue = 3
  • print("now")              i = 3
  • defer
  • i -= 1
  • willSet                        value = 2
  • willSet                        newValue = 2 // newValue is correctly passed
  • willSet                        i = 3 // after executing willSet, i is NOT updated to newValue : that's the bug
Not expected result when changing a variable in a "defer" block, if the variable has observers willSet or didSet.
 
 
Q