[Pitch] Limit Implicit Capture


(Daniel Duan) #1

I'm curious to see if anyone else has desire for this change.

Currently, scopes created by functions, closures, "do {}", etc.
implicitly capture values from their outer scopes. The only way to opt out
this behavior is via functions defined "elsewhere":

func a() { ... }
func foo(b: () -> ()) {
    func c() { ... }
    let d = { ... }

    a() // nothing from foo's scope will implicitly get into a
    b() // nothing from foo's scope will implicitly get into b

    c() // implicitly captures values in foo
    d() // implicitly captures values in foo
    do {
        // implicitly captures values in foo
    }
}

One problem that comes with this bebavior is unintended capturing. E.g. a user
may think they successfuly factored out some code, but a missing variable was
satified by something with the same name from an outer scope.

C++ addresses this issue by making its user explicitly indicate lambda's
capturing behavior:

[] {...} // capture nothing
[=] {...} // capture everything by value
[&] {...} // capture everything by reference

It'd be nice if Swift can allow user to opt out the automatic capturing at
some level. We already have the capture list syntax, reusing it for explictly
capture in this case:

func foo() {
    let a = 5
    let b = "Ziggy"
    let c = ["Weild", "Gilly"]

    let d: @explicit_capture () -> () = { [a, b] in
        let years = a // ok
        let artist = b // ok
        let others = c // error: implicit capture in not allowed for 'd'
    }
}

An alternative would be making implicit capture an opt-in feature (similar to
C++):

func foo() {
    let a = 5
    let b = "Ziggy"
    let c = ["Weild", "Gilly"]

    let d = { [a] in
        let years = a // ok
        let artist = b // error: implicit capture in not allowed for 'd'
        let others = c // error: implicit capture in not allowed for 'd'
    }

    let e: @capture_all () -> () = { [a] in
        let years = a // ok
        let artist = b // ok
        let others = c // error: implicit capture in not allowed for 'e'
    }
}

Obviously, this version would be a breaking change.

I have no attchment to the syntax. Chris has brought up moving @noescape
before variable types declaration, so putting @explicit_capture there seems
natural.

Thoughts?


(Daniel Duan) #2

Daniel Duan via swift-evolution <swift-evolution@...> writes:

    let e: <at> capture_all () -> () = { [a] in

        let others = c // error: implicit capture in not allowed for 'e'
    }

Oops, I made an mistake in this example, 'c' here would not cause error since
user opts in implicit capturing. My apologies.


(Haravikk) #3

I think I’m in favour of something along these lines, though personally I think the better solution is to simply eliminate implicit capture for closures and local functions, and instead provide a @capture attribute to re-enable current behaviour (and to avoid breaking code). This way developers are encouraged to either opt-in to the implicit capture behaviour, or declare a capture list, with the latter being preferred as it can more clearly declare what you need and in what capacity.

That said I think that do { … } blocks should retain implicit capture, as they’re really just a sub-section of your code, even if in some ways they can be thought of as a closure that automatically executes.

···

On 14 Mar 2016, at 02:12, Daniel Duan via swift-evolution <swift-evolution@swift.org> wrote:

I'm curious to see if anyone else has desire for this change.

Currently, scopes created by functions, closures, "do {}", etc.
implicitly capture values from their outer scopes. The only way to opt out
this behavior is via functions defined "elsewhere":

func a() { ... }
func foo(b: () -> ()) {
   func c() { ... }
   let d = { ... }

   a() // nothing from foo's scope will implicitly get into a
   b() // nothing from foo's scope will implicitly get into b

   c() // implicitly captures values in foo
   d() // implicitly captures values in foo
   do {
       // implicitly captures values in foo
   }
}

One problem that comes with this bebavior is unintended capturing. E.g. a user
may think they successfuly factored out some code, but a missing variable was
satified by something with the same name from an outer scope.

C++ addresses this issue by making its user explicitly indicate lambda's
capturing behavior:

[] {...} // capture nothing
[=] {...} // capture everything by value
[&] {...} // capture everything by reference

It'd be nice if Swift can allow user to opt out the automatic capturing at
some level. We already have the capture list syntax, reusing it for explictly
capture in this case:

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d: @explicit_capture () -> () = { [a, b] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'd'
   }
}

An alternative would be making implicit capture an opt-in feature (similar to
C++):

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d = { [a] in
       let years = a // ok
       let artist = b // error: implicit capture in not allowed for 'd'
       let others = c // error: implicit capture in not allowed for 'd'
   }

   let e: @capture_all () -> () = { [a] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'e'
   }
}

Obviously, this version would be a breaking change.

I have no attchment to the syntax. Chris has brought up moving @noescape
before variable types declaration, so putting @explicit_capture there seems
natural.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #4

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

Jordan

···

On Mar 13, 2016, at 19:12, Daniel Duan via swift-evolution <swift-evolution@swift.org> wrote:

I'm curious to see if anyone else has desire for this change.

Currently, scopes created by functions, closures, "do {}", etc.
implicitly capture values from their outer scopes. The only way to opt out
this behavior is via functions defined "elsewhere":

func a() { ... }
func foo(b: () -> ()) {
   func c() { ... }
   let d = { ... }

   a() // nothing from foo's scope will implicitly get into a
   b() // nothing from foo's scope will implicitly get into b

   c() // implicitly captures values in foo
   d() // implicitly captures values in foo
   do {
       // implicitly captures values in foo
   }
}

One problem that comes with this bebavior is unintended capturing. E.g. a user
may think they successfuly factored out some code, but a missing variable was
satified by something with the same name from an outer scope.

C++ addresses this issue by making its user explicitly indicate lambda's
capturing behavior:

[] {...} // capture nothing
[=] {...} // capture everything by value
[&] {...} // capture everything by reference

It'd be nice if Swift can allow user to opt out the automatic capturing at
some level. We already have the capture list syntax, reusing it for explictly
capture in this case:

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d: @explicit_capture () -> () = { [a, b] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'd'
   }
}

An alternative would be making implicit capture an opt-in feature (similar to
C++):

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d = { [a] in
       let years = a // ok
       let artist = b // error: implicit capture in not allowed for 'd'
       let others = c // error: implicit capture in not allowed for 'd'
   }

   let e: @capture_all () -> () = { [a] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'e'
   }
}

Obviously, this version would be a breaking change.

I have no attchment to the syntax. Chris has brought up moving @noescape
before variable types declaration, so putting @explicit_capture there seems
natural.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #5

I like the idea, but this seems to me like more a property of the closure literal than the function type, so the annotation belongs in the capture list. {[only a, b] in a + b } maybe (filling in your own preferred syntax for 'only').

-Joe

···

On Mar 13, 2016, at 7:12 PM, Daniel Duan via swift-evolution <swift-evolution@swift.org> wrote:

I'm curious to see if anyone else has desire for this change.

Currently, scopes created by functions, closures, "do {}", etc.
implicitly capture values from their outer scopes. The only way to opt out
this behavior is via functions defined "elsewhere":

func a() { ... }
func foo(b: () -> ()) {
   func c() { ... }
   let d = { ... }

   a() // nothing from foo's scope will implicitly get into a
   b() // nothing from foo's scope will implicitly get into b

   c() // implicitly captures values in foo
   d() // implicitly captures values in foo
   do {
       // implicitly captures values in foo
   }
}

One problem that comes with this bebavior is unintended capturing. E.g. a user
may think they successfuly factored out some code, but a missing variable was
satified by something with the same name from an outer scope.

C++ addresses this issue by making its user explicitly indicate lambda's
capturing behavior:

[] {...} // capture nothing
[=] {...} // capture everything by value
[&] {...} // capture everything by reference

It'd be nice if Swift can allow user to opt out the automatic capturing at
some level. We already have the capture list syntax, reusing it for explictly
capture in this case:

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d: @explicit_capture () -> () = { [a, b] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'd'
   }
}

An alternative would be making implicit capture an opt-in feature (similar to
C++):

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d = { [a] in
       let years = a // ok
       let artist = b // error: implicit capture in not allowed for 'd'
       let others = c // error: implicit capture in not allowed for 'd'
   }

   let e: @capture_all () -> () = { [a] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'e'
   }
}

Obviously, this version would be a breaking change.

I have no attchment to the syntax. Chris has brought up moving @noescape
before variable types declaration, so putting @explicit_capture there seems
natural.

Thoughts?


(Joe Groff) #6

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

`[var x]` seems to me like a reasonable spelling for explicit `var` capture.

-Joe

···

On Mar 16, 2016, at 11:09 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Jordan

On Mar 13, 2016, at 19:12, Daniel Duan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'm curious to see if anyone else has desire for this change.

Currently, scopes created by functions, closures, "do {}", etc.
implicitly capture values from their outer scopes. The only way to opt out
this behavior is via functions defined "elsewhere":

func a() { ... }
func foo(b: () -> ()) {
   func c() { ... }
   let d = { ... }

   a() // nothing from foo's scope will implicitly get into a
   b() // nothing from foo's scope will implicitly get into b

   c() // implicitly captures values in foo
   d() // implicitly captures values in foo
   do {
       // implicitly captures values in foo
   }
}

One problem that comes with this bebavior is unintended capturing. E.g. a user
may think they successfuly factored out some code, but a missing variable was
satified by something with the same name from an outer scope.

C++ addresses this issue by making its user explicitly indicate lambda's
capturing behavior:

[] {...} // capture nothing
[=] {...} // capture everything by value
[&] {...} // capture everything by reference

It'd be nice if Swift can allow user to opt out the automatic capturing at
some level. We already have the capture list syntax, reusing it for explictly
capture in this case:

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d: @explicit_capture () -> () = { [a, b] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'd'
   }
}

An alternative would be making implicit capture an opt-in feature (similar to
C++):

func foo() {
   let a = 5
   let b = "Ziggy"
   let c = ["Weild", "Gilly"]

   let d = { [a] in
       let years = a // ok
       let artist = b // error: implicit capture in not allowed for 'd'
       let others = c // error: implicit capture in not allowed for 'd'
   }

   let e: @capture_all () -> () = { [a] in
       let years = a // ok
       let artist = b // ok
       let others = c // error: implicit capture in not allowed for 'e'
   }
}

Obviously, this version would be a breaking change.

I have no attchment to the syntax. Chris has brought up moving @noescape
before variable types declaration, so putting @explicit_capture there seems
natural.

Thoughts?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daniel Duan) #7

Joe Groff via swift-evolution <swift-evolution@...> writes:

annotation belongs in the capture list. {[only a, b] in a + b } maybe

-Joe

How about { ![a, b] in a + b } ?


(Jordan Rose) #8

I forgot to preempt that one too. :slight_smile: That would be somewhat at odds with the "var x" we left in switches, which is definitely an independent variable. (Especially if someone extends it to "var x = y".)

Jordan

···

On Mar 16, 2016, at 11:11, Joe Groff <jgroff@apple.com> wrote:

On Mar 16, 2016, at 11:09 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

`[var x]` seems to me like a reasonable spelling for explicit `var` capture.


(Joe Groff) #9

OTOH it does more or less exactly what it says, capturing the `var` x rather than only the current value of x. It also makes some sense with the new restriction on capturing `inout` parameters—you can't capture an `inout x` using `var x` since you don't get to see the `var` through the inout abstraction. As far as `[var x = y]` is concerned, we could just say that's not allowed.

-Joe

···

On Mar 16, 2016, at 11:24 AM, Jordan Rose <jordan_rose@apple.com> wrote:

On Mar 16, 2016, at 11:11, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 16, 2016, at 11:09 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

`[var x]` seems to me like a reasonable spelling for explicit `var` capture.

I forgot to preempt that one too. :slight_smile: That would be somewhat at odds with the "var x" we left in switches, which is definitely an independent variable. (Especially if someone extends it to "var x = y".)


(Haravikk) #10

What about “capture” as the keyword? This will require a new name for capture lists, but it seems like it isn’t well named at the moment anyway if it’s effectively copying values right now.

I’m also curious about thoughts regarding making implicit capture opt-in by default, vs the original proposals opt-out solution. Obviously this will require a good solution to the capture lists to replace the functionality and/or an attribute to opt-in to the current implicit behaviour, but I think it’s safer for it to be opt-in rather than the default.

···

On 16 Mar 2016, at 18:24, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 16, 2016, at 11:11, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 16, 2016, at 11:09 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

`[var x]` seems to me like a reasonable spelling for explicit `var` capture.

I forgot to preempt that one too. :slight_smile: That would be somewhat at odds with the "var x" we left in switches, which is definitely an independent variable. (Especially if someone extends it to "var x = y".)


(Joe Groff) #11

I suspect that by far the most common use for capture lists is to declare [(weak|unowned) self]. I don't think we want to punish that use case.

-Joe

···

On Mar 16, 2016, at 12:30 PM, Haravikk <swift-evolution@haravikk.me> wrote:

On 16 Mar 2016, at 18:24, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 16, 2016, at 11:11, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 16, 2016, at 11:09 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's worth noting that—for better or for worse—explicit capture has different semantics from implicit capture today. If a local variable ('var', not 'let') is captured, it is captured by value when mentioned explicitly and by reference when not. This is discussed in The Swift Programming Language <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544>.

If you were to then propose a syntax of `inout x` or `&x`, I would argue that there is no inout-ish behavior: updates to the variable both inside and outside the closure (a) are always reflected immediately (i.e. there is no writeback), and (b) are not subject to the aliasing restrictions that 'inout' has.

(Not that I have an alternative spelling handy.)

`[var x]` seems to me like a reasonable spelling for explicit `var` capture.

I forgot to preempt that one too. :slight_smile: That would be somewhat at odds with the "var x" we left in switches, which is definitely an independent variable. (Especially if someone extends it to "var x = y".)

What about “capture” as the keyword? This will require a new name for capture lists, but it seems like it isn’t well named at the moment anyway if it’s effectively copying values right now.

I’m also curious about thoughts regarding making implicit capture opt-in by default, vs the original proposals opt-out solution. Obviously this will require a good solution to the capture lists to replace the functionality and/or an attribute to opt-in to the current implicit behaviour, but I think it’s safer for it to be opt-in rather than the default.


(Haravikk) #12

capture could remain implicit for weak/unowned. Actually anything else for weak/owned may actually be meaningless (if you create a weak reference to a new copy of a value type then it’s just going to be nil I think, I could be mistaken).

···

On 16 Mar 2016, at 19:37, Joe Groff <jgroff@apple.com> wrote:

What about “capture” as the keyword? This will require a new name for capture lists, but it seems like it isn’t well named at the moment anyway if it’s effectively copying values right now.

I’m also curious about thoughts regarding making implicit capture opt-in by default, vs the original proposals opt-out solution. Obviously this will require a good solution to the capture lists to replace the functionality and/or an attribute to opt-in to the current implicit behaviour, but I think it’s safer for it to be opt-in rather than the default.

I suspect that by far the most common use for capture lists is to declare [(weak|unowned) self]. I don't think we want to punish that use case.