Nested functions and reference capturing

130 views
Skip to first unread message

Bruno Berisso

unread,
Nov 27, 2015, 11:29:22 AM11/27/15
to Swift Language
Hi,

I just realice that in nested functions the reference to self is not mandatory. For example:

class Foo {

   
var bar:Int

    func functionA
() {
        func nested
() {
            bar
++

       
}
        externalFunction
(nested)
   
}

    func functionB
() -> (Int -> Int) {
        func nested
(x:Int) -> Int {
           
return bar + x
       
}
       
return nested
   
}
}

The equivalent implementation with closures instead of functions require self in every reference to bar as expected. What's happen here with those invisible references?

Brent Royal-Gordon

unread,
Nov 27, 2015, 12:10:21 PM11/27/15
to Bruno Berisso, Swift Language
This is an artificial limitation introduced to make it more obvious when you're capturing self. 

Basically, the problem is that this:

    myProperty = { myMethod() }

Creates a retain cycle. By requiring you to say "self.myMethod()", Swift's designers hope to make it more obvious that the closure is capturing self. 

Sent from my iPad
--
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/7eabe4f2-9e8a-40b6-9232-0eed6f7b1bf3%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bruno Berisso

unread,
Nov 27, 2015, 12:49:42 PM11/27/15
to Brent Royal-Gordon, Swift Language
I know that the requirement of self is just to make clear that you are retaining it, but in closures you can also specify if you want an unowned or weak reference to self. 

How do you that with nested functions? the syntax for closures doesn't seems to work.

Bruno Berisso

unread,
Dec 1, 2015, 9:31:49 AM12/1/15
to Swift Language, br...@architechies.com
I think I found something. If my calculous are correct the nested functions approach hold an strong reference to self silently. What I do was look at the intermediate representation of this example and try yo figure out what's going on with the implicit reference to self:

class Test {

   
var bar:Int = 0

    func functionA
() -> (() -> ()) {
        func nestedA
() {
            bar
++
       
}
       
return nestedA
   
}

    func closureA
() -> (() -> ()) {
        let nestedClosureA
= { [unowned self] () -> () in
           
self.bar++
       
}
       
return nestedClosureA
   
}
}

I change my original example a little for simplicity. To get the intermediate representation I use:
xcrun swiftc -emit-silgen test.swift | xcrun swift-demangle > test.silgen

Here is what I think are the important parts, the full file is attached:
--- This is part of the implementation of the method 'functionA'. There is a 'strong_retain %0' that get a strong reference to self and pass it to %2 which is a reference to the nested function obtained in the line before. Also, the type of 'partial_apply %2(%0)' tells that the parameter has type "@owned Test".


....


--- Get a constant reference to self.
  debug_value
%0 : $Test  // let self             // id: %1


--- Get a reference to the nested function.
 
%2 = function_ref @test.Test.(functionA (test.Test) -> () -> () -> ()).(nestedA #1) ()() : $@convention(thin) (@owned Test) -> () // user: %4


--- Make the self reference strong.
  strong_retain
%0 : $Test                        // id: %3


--- Partially apply the nested function. This result in a function without parameters. Note the "@owned Test" in the type of the parameter
 
%4 = partial_apply %2(%0) : $@convention(thin) (@owned Test) -> () // user: %5
--- And the "@callee_owned" in the type of the result function
 
return %4 : $@callee_owned () -> ()            // id: %5
}

....


--- This is part of the implementation of 'closureA'. This code is a little messy but basically it create a new reference container with "alloc_box" and store an unowned reference to self there. Then partially apply the closure with the unowned argument and return the result.

....

--- Get a constant reference to self
  debug_value
%0 : $Test  // let self             // id: %1


--- Store an unowned reference to self in a box
 
%2 = alloc_box $@sil_unowned Test  // let self  // users: %5, %7, %15
 
%3 = ref_to_unowned %0 : $Test to $@sil_unowned Test // users: %4, %5
  unowned_retain
%3 : $@sil_unowned Test          // id: %4
  store
%3 to %2#1 : $*@sil_unowned Test          // id: %5


--- Get a reference to the nested closure
 
%6 = function_ref @test.Test.(closureA (test.Test) -> () -> () -> ()).(closure #1) : $@convention(thin) (@owned @sil_unowned Test) -> () // user: %13


--- Read the unowned reference from the box
 
%7 = load %2#1 : $*@sil_unowned Test            // users: %8, %9
  strong_retain_unowned
%7 : $@sil_unowned Test   // id: %8
 
%9 = unowned_to_ref %7 : $@sil_unowned Test to $Test // users: %10, %12
 
%10 = ref_to_unowned %9 : $Test to $@sil_unowned Test // users: %11, %13
  unowned_retain
%10 : $@sil_unowned Test         // id: %11
  strong_release
%9 : $Test                       // id: %12


--- Partially apply the nested closure, this time the type of the closure is "@owned @sil_unowned Test"
 
%13 = partial_apply %6(%10) : $@convention(thin) (@owned @sil_unowned Test) -> () // users: %14, %16
  debug_value
%13 : $@callee_owned () -> ()  // let nestedClosureA // id: %14
  strong_release
%2#0 : $@box @sil_unowned Test   // id: %15
 
return %13 : $@callee_owned () -> ()            // id: %16
}


So my conclusion is: don't use nested functions the way Apple does in the Swift manual because it hold a strong reference to self without tell you anything. If anyone with more solid knowledge of SIL could look at this and give an answer it will be great.

Thanks!
To unsubscribe from this group and stop receiving emails from it, send an email to swift-language+unsubscribe@googlegroups.com.
To post to this group, send email to swift-language@googlegroups.com.
test.swift
test.silgen

Chris Lattner

unread,
Dec 1, 2015, 1:23:38 PM12/1/15
to Bruno Berisso, Swift Language, br...@architechies.com

On Dec 1, 2015, at 6:31 AM, Bruno Berisso <goja...@gmail.com> wrote:

I think I found something. If my calculous are correct the nested functions approach hold an strong reference to self silently. 

Yes, this is the current (and correct) behavior.  You’re right that this can cause a surprising capture of self, which can lead to reference cycles.  To combat that, we have briefly considered requiring “self.” qualification within nested functions (just like we do within no-escape closures) but haven’t had time to do the implementation work to make sure it isn’t totally annoying.  

To roll this out, we’d want to do some local analysis of nested functions, to infer when they are “obviously” only called (not escaping) and not require “self." qualification there (since direct-calls-only are pretty common in some people’s use of nested functions).  This isn’t an incredibly difficult analysis, we just haven’t had time to implement it.

-Chris

Bruno Berisso

unread,
Dec 1, 2015, 1:25:54 PM12/1/15
to Chris Lattner, Swift Language, br...@architechies.com
Really appreciate your answer.
Thanks!
Reply all
Reply to author
Forward
0 new messages