Home > front end >  Why does this closure code throw an error when not using a capture list?
Why does this closure code throw an error when not using a capture list?

Time:03-20

func biggerOne(_ a : Int, _ b : Int) -> Int? {
    if a == b {
        return nil
    } else if a > b {
        return a
    } else {
        return b
    }
}

var someClosure : (Int, Int) -> Int? = biggerOne(_:_:)

// Not working
someClosure = { (left : Int, right : Int) in
    someClosure(left , right)
}
print(someClosure(2,3)!)

// Working
someClosure = { [someClosure] (left : Int, right : Int) in
    someClosure(left , right)
}
print(someClosure(2,3)!)

I knew that closure uses capture list to store values ​​on creation so can solve the problem but why is the code above not working?

If you have any ideas, I would appreciate your help.

CodePudding user response:

Without capturing, someClosure here is simply calling itself, and then causing a stack overflow:

someClosure = { (left : Int, right : Int) in
   someClosure(left , right) // this call refers to the new value of "someClosure"
}

(I'm not sure if the fact that this passes compilation is intentional.)

This is just like as if you have defined a named function like:

func someClosure(left: Int, right: Int): Int? {
    someClosure(left: left, right: right)
}

On the other hand, the capture list captures the old value of someClosure, so "someClosure" inside the closure body refers to the captured value (i.e. the old value), and there is no infinite recursion. You can think of this as:

let captured = someClosure
someClosure = { (left : Int, right : Int) in
    captured(left , right)
}

Using lldb yourExecutable and the thread backtrace command, you can see the many duplicated stack frames that are caused by the recursion.

(lldb) thread backtrace 
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ff7bf6ffff8)
  * frame #0: 0x00007ff822e4fa99 libswiftCore.dylib`swift_beginAccess   41
    frame #1: 0x0000000100003d87 yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   71
    frame #2: 0x0000000100003dbc yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   124
    frame #3: 0x0000000100003dbc yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   124
    frame #4: 0x0000000100003dbc yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   124
    ...
    frame #65515: 0x0000000100003dbc yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   124
    frame #65516: 0x0000000100003dbc yourExecutable`$s14yourExecutableSiSgSi_SitcfU_   124
    frame #65517: 0x0000000100003baa yourExecutable`main   218
    frame #65518: 0x00000001000154fe dyld`start   462

CodePudding user response:

There are circular references, someClosure is calling itself.

Try eliminating the circular references:

        // Now it works
        let firstClosure = { (left : Int, right : Int) in
            someClosure(left , right)
        }
        print(firstClosure(2,3)!)
        
        // Working
        let secondClosure = { [someClosure] (left : Int, right : Int) in
            someClosure(left , right)
        }
        print(secondClosure(2,3)!)

  • Related