`defer` can take variables declared after itself: Bug or Feature?

Until Swift 5.1 this piece of code will cause build error, which totally makes sense:

do {
    defer { print(a) } // `a` is not declared at this point
    let a = 0
}

But from Swift 5.2, the same code can be compiled with a warning message that says a was never used; and from Swift 5.3 even the warning message disappears.

I tried to search for proposals related to this modification but had no luck.

6 Likes

I might be missing something, but I don't see why it shouldn't compile.

This compiles and behaves as expected:

func test() {
  if Bool.random() { return }
  defer { print(a) }
  let a = 123
}

test()

And this results in an error, also as expected:

func test() {
  defer { print(a) } // Error: 'defer' block captures 'a' before it is declared
  if Bool.random() { return }
  let a = 123
}

test()

I noticed that the following program crashes the compiler when compiled with -O (but works fine in debug) though:

func test() {
  defer { a += 1; print(a) }
  var a = 123
}

test()
% swiftc --version
Apple Swift version 5.4.2 (swiftlang-1205.0.28.2 clang-1205.0.19.57)
Target: x86_64-apple-darwin20.5.0

% swiftc test.swift 
% ./test
124

% swiftc -O test.swift
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project and the crash backtrace.
Stack dump:
...

Because it gets confusing with shadowing:

func test(_ flag: Bool) {
  let a = 1
  if flag {
    defer { print(a) } // prints 1
    let a = 2
  }
}
test(true)
2 Likes

Fun exercice: can we guess what this one will print?

func test(a: Int = 456) {
  defer { print(a) }
  let a = 123
}

test()

Result:

456
3 Likes

So what should the correct behavior be? None of these examples should compile?

  • with shadowing
func test(a: Int = 456) {
    defer { print(a) } //456
    let a = 123
    defer { print(a) } //123
}
123
456

  • without shadowing
func test(x: Int = 456) {
    defer { print(a) } //123
    let a = 123
    defer { print(a) } //123
}
123
123
1 Like

It seemed a reasonable feature until Jordan pointed out the shadowing problem. Obviously, it wouldn't be good to silently change the behavior of existing code, and one would want let a = 123; defer { print(a) } not to change because of code that's written below it. I'd conclude, then, that the best path forward with the examples of use before declaration would be a deprecation warning that eventually becomes a compilation error again.

5 Likes

Yeah, I agree -- this seems like a compiler bug to me.

3 Likes

Filed [SR-15013] Issue diagnostic for defer relying on incorrect scoping behavior · Issue #57342 · apple/swift · GitHub to track the fix for this.

4 Likes