Nested functions and reference capturing

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Nested functions and reference capturing

Bruno Berisso
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?

--
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 [hidden email].
To post to this group, send email to [hidden email].
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.
Reply | Threaded
Open this post in threaded view
|

Re: Nested functions and reference capturing

Brent Royal-Gordon
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

On Nov 27, 2015, at 8:29 AM, Bruno Berisso <[hidden email]> wrote:

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?

--
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 [hidden email].
To post to this group, send email to [hidden email].
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.

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/B9AE8767-C615-4AC9-A47C-2D9B28458B14%40architechies.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Nested functions and reference capturing

Bruno Berisso
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.

El vie., 27 de nov. de 2015 a la(s) 2:10 p. m., Brent Royal-Gordon <[hidden email]> escribió:
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

On Nov 27, 2015, at 8:29 AM, Bruno Berisso <[hidden email]> wrote:

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?

--
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 [hidden email].
To post to this group, send email to [hidden email].
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.

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/CA%2BaYCtf7ovN61-WtYxWX2-Tqr0esn59EKfngLDTpMxuoXMP%2Bvg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Nested functions and reference capturing

Bruno Berisso
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!


On Friday, November 27, 2015 at 2:49:42 PM UTC-3, Bruno Berisso wrote:
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.

El vie., 27 de nov. de 2015 a la(s) 2:10 p. m., Brent Royal-Gordon <[hidden email]> escribió:
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

On Nov 27, 2015, at 8:29 AM, Bruno Berisso <[hidden email]> wrote:

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?

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit <a href="https://groups.google.com/d/msgid/swift-language/7eabe4f2-9e8a-40b6-9232-0eed6f7b1bf3%40googlegroups.com?utm_medium=email&amp;utm_source=footer" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://groups.google.com/d/msgid/swift-language/7eabe4f2-9e8a-40b6-9232-0eed6f7b1bf3%40googlegroups.com?utm_medium\75email\46utm_source\75footer&#39;;return true;" onclick="this.href=&#39;https://groups.google.com/d/msgid/swift-language/7eabe4f2-9e8a-40b6-9232-0eed6f7b1bf3%40googlegroups.com?utm_medium\75email\46utm_source\75footer&#39;;return true;">https://groups.google.com/d/msgid/swift-language/7eabe4f2-9e8a-40b6-9232-0eed6f7b1bf3%40googlegroups.com.
For more options, visit <a href="https://groups.google.com/d/optout" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://groups.google.com/d/optout&#39;;return true;" onclick="this.href=&#39;https://groups.google.com/d/optout&#39;;return true;">https://groups.google.com/d/optout.

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/eb41235a-ee2a-4162-8a97-4849281f1337%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

test.swift (334 bytes) Download Attachment
test.silgen (23K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Nested functions and reference capturing

Chris Lattner

On Dec 1, 2015, at 6:31 AM, Bruno Berisso <[hidden email]> 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

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/7436A222-FC38-429B-813E-941E90B1D7B8%40apple.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Nested functions and reference capturing

Bruno Berisso
Really appreciate your answer.
Thanks!

On Tue, Dec 1, 2015 at 3:23 PM Chris Lattner <[hidden email]> wrote:

On Dec 1, 2015, at 6:31 AM, Bruno Berisso <[hidden email]> 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

--
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 [hidden email].
To post to this group, send email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/CA%2BaYCtc68wxFwGPVuqBKFs2wTi1aFWjp%3DgQMtb7c_9hUuwm6Dg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.