Update the signature of ObjectiveC.autoreleasepool [SR-842]


(Timothy J. Wood) #1

In preparation for writing a proposal, I wanted to solicit any feedback and general level of interest on addressing SR-842, which proposes modifying ObjectiveC.autoreleasepool to allow a potentially `throw`ing body via a `rethrows` annotation. I have a branch with the very simple change applied and a test. However, Dmitri Gribenko pointed out that it would be even better if the signature was amended to allow for a generic return type:

  public func autoreleasepool<Result>(@noescape code: () throws -> Result) rethrows -> Result

It isn’t clear to me whether it is possible for a wrapper to be written that adds rethrow, since the function needs to compile under the assumption that the passed block does not throw. So, something like this doesn’t actually compile.

  func autoreleasepool_generic<ResultType>(@noescape code: Void throws -> ResultType) rethrows -> ResultType {
  
    // or some Result enum...
    var result:ResultType?
    var error:ErrorProtocol?

    autoreleasepool {
      do {
        result = try code()
      } catch let e {
        error = e
      }
    }
  
    if let result = result {
      return result
    }
  
    // Doesn't work, since in the case that `code` doesn't throw, the whole function can't.
    throw error!
  }

Is there a way I’m missing to write a rethrows wrapper around autoreleasepool that would provide a fair comparison?

At any rate, it seems much nicer to be able to do:

  let value = try autoreleasepool {…}

than to have a bunch of boilerplate at the call site, or for everyone to figure out how to write their own wrapper.

Some other questions for feedback that I came across while looking into this:

- Since the API is already changing, should the `code` parameter be renamed to `body`? The rest of stdlib uses `body` frequently and never uses `code` as far as I can see.
- Should the name of the generic argument be something like ‘Result’ (which might conflict with a enum that holds a value-or-error) or ‘ResultType` (which might conflict with a protocol in 2.2 land, but less so in 3.0 maybe?), or something else?

Thanks!

-tim


(Dmitri Gribenko) #2

Hi Timothy,

I think it should be possible -- or I wouldn't be suggesting it :slight_smile:

func poolPush() {}
func poolPop() {}

public func autoreleasepool<Result>(@noescape code: () throws ->
Result) rethrows -> Result {
  poolPush()
  defer { poolPop() }
  return try code()
}

Dmitri

···

On Sun, Mar 20, 2016 at 9:32 PM, Timothy Wood via swift-evolution <swift-evolution@swift.org> wrote:

In preparation for writing a proposal, I wanted to solicit any feedback and general level of interest on addressing SR-842, which proposes modifying ObjectiveC.autoreleasepool to allow a potentially `throw`ing body via a `rethrows` annotation. I have a branch with the very simple change applied and a test. However, Dmitri Gribenko pointed out that it would be even better if the signature was amended to allow for a generic return type:

  public func autoreleasepool<Result>(@noescape code: () throws -> Result) rethrows -> Result

It isn’t clear to me whether it is possible for a wrapper to be written that adds rethrow, since the function needs to compile under the assumption that the passed block does not throw. So, something like this doesn’t actually compile.

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Jordan Rose) #3

-1 for the signature change. The most common case of autoreleasepool does not return a value and has a multi-statement body that prevents the result type from being inferred. This needs to continue to work:

autoreleasepool {
  foo()
  bar()
}

If we had a rule to default generic return values to Void if they aren't used then I'd be okay with it, but that'd be a separate proposal. (Providing two overloads may also work; haven't tested it.)

Jordan

···

On Mar 20, 2016, at 21:32 , Timothy Wood via swift-evolution <swift-evolution@swift.org> wrote:

In preparation for writing a proposal, I wanted to solicit any feedback and general level of interest on addressing SR-842, which proposes modifying ObjectiveC.autoreleasepool to allow a potentially `throw`ing body via a `rethrows` annotation. I have a branch with the very simple change applied and a test. However, Dmitri Gribenko pointed out that it would be even better if the signature was amended to allow for a generic return type:

public func autoreleasepool<Result>(@noescape code: () throws -> Result) rethrows -> Result

It isn’t clear to me whether it is possible for a wrapper to be written that adds rethrow, since the function needs to compile under the assumption that the passed block does not throw. So, something like this doesn’t actually compile.

func autoreleasepool_generic<ResultType>(@noescape code: Void throws -> ResultType) rethrows -> ResultType {

   // or some Result enum...
   var result:ResultType?
   var error:ErrorProtocol?

   autoreleasepool {
     do {
       result = try code()
     } catch let e {
       error = e
     }
   }

   if let result = result {
     return result
   }

   // Doesn't work, since in the case that `code` doesn't throw, the whole function can't.
   throw error!
}

Is there a way I’m missing to write a rethrows wrapper around autoreleasepool that would provide a fair comparison?

At any rate, it seems much nicer to be able to do:

  let value = try autoreleasepool {…}

than to have a bunch of boilerplate at the call site, or for everyone to figure out how to write their own wrapper.

Some other questions for feedback that I came across while looking into this:

- Since the API is already changing, should the `code` parameter be renamed to `body`? The rest of stdlib uses `body` frequently and never uses `code` as far as I can see.
- Should the name of the generic argument be something like ‘Result’ (which might conflict with a enum that holds a value-or-error) or ‘ResultType` (which might conflict with a protocol in 2.2 land, but less so in 3.0 maybe?), or something else?

Thanks!

-tim

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


(Timothy J. Wood) #4

Sorry; yes of course it is possible to make stdlib do this. What I meant was whether it is possible for code outside stdlib to easily wrap a autoreleasepool that does not have `rethrow` applied and make one that does. Or, more generically, is it possible in Swift, given only `func f(@noescape body: Void -> Void)` to write `func f(@noescape body: Void throws -> Void) rethrows`? It seemed to me like it isn’t possible, since the wrapper needs to capture the error inside a do/catch in a var and then optionally re-throw it outside the do/catch, but the rethrow can’t be done (as far as I can see) since that seems to make the compiler thing that the function should be marked as `throws` instead of `rethrows`.

In particular, in this case pushPool() and popPool() don’t exist as nice public API:

@warn_unused_result
@_silgen_name("objc_autoreleasePoolPush")
func __pushAutoreleasePool() -> OpaquePointer

@_silgen_name("objc_autoreleasePoolPop")
func __popAutoreleasePool(pool: OpaquePointer)

Either way, I think stdlib should be marked `rethrow` here, but it may make for a more compelling argument for this (and other cases in stdlib that take a function argument) if it is hard or impossible(?) for code outside stdlib to provide a `rethrows` wrapper of code in stdlib.

Thanks!

-tim

···

On Mar 20, 2016, at 11:41 PM, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Sun, Mar 20, 2016 at 9:32 PM, Timothy Wood via swift-evolution > <swift-evolution@swift.org> wrote:

In preparation for writing a proposal, I wanted to solicit any feedback and general level of interest on addressing SR-842, which proposes modifying ObjectiveC.autoreleasepool to allow a potentially `throw`ing body via a `rethrows` annotation. I have a branch with the very simple change applied and a test. However, Dmitri Gribenko pointed out that it would be even better if the signature was amended to allow for a generic return type:

public func autoreleasepool<Result>(@noescape code: () throws -> Result) rethrows -> Result

It isn’t clear to me whether it is possible for a wrapper to be written that adds rethrow, since the function needs to compile under the assumption that the passed block does not throw. So, something like this doesn’t actually compile.

Hi Timothy,

I think it should be possible -- or I wouldn't be suggesting it :slight_smile:

func poolPush() {}
func poolPop() {}

public func autoreleasepool<Result>(@noescape code: () throws ->
Result) rethrows -> Result {
poolPush()
defer { poolPop() }
return try code()
}


(Timothy J. Wood) #5

Thanks for pointing this case out.

It looks like from a quick check that Swift will allow for two versions of a function that differ in a function parameter return type / throw type, and will pick the right one. At least, this produces what seems like reasonable results in Xcode 7.3b5 with the default snapshot (I’ve not tested with 3.0 yet, but presumably it would be a regression for it to not work there):

  https://gist.github.com/tjw/4904ac82f84adf9f2292

Of course, if anyone sees cases that I missed checking I’d appreciate notes on that.

This does promote to the `rethrows` version if there is any return type, but does so w/o compile warnings/errors, but I’m presuming that this doesn’t add undue runtime performance overhead. In my experience, call sites that are explicitly using pools are allocation-heavy (kinda the whole point =) and any micro-benchmark change here wouldn’t be significant.

I can amend the proposed API change with the addition of a new version of `autoreleasepool` that adds a return value and rethrows annotation and leaves the Void -> Void version.

As a side note, checking one of our products 28 of 123 uses of @autoreleasepool in ObjC-land are things that would benefit from a return value. Other code bases may differ, but it seems like a significant amount of code could be cleaned up (but an even bigger chunk wouldn’t want to deal with the implicit non-Void return case!)

Thanks,

-tim

···

On Mar 21, 2016, at 9:37 AM, Jordan Rose <jordan_rose@apple.com> wrote:

-1 for the signature change. The most common case of autoreleasepool does not return a value and has a multi-statement body that prevents the result type from being inferred. This needs to continue to work:

autoreleasepool {
  foo()
  bar()
}

If we had a rule to default generic return values to Void if they aren't used then I'd be okay with it, but that'd be a separate proposal. (Providing two overloads may also work; haven't tested it.)

Jordan


(Dmitri Gribenko) #6

It seems to work for me:

func poolPush() {}
func poolPop() {}

public func autoreleasepool<Result>(@noescape code: () throws ->
Result) rethrows -> Result {
  poolPush()
  defer { poolPop() }
  return try code()
}

func foo() {}
func bar() {}

autoreleasepool {
  foo()
  bar()
}

Dmitri

···

On Mon, Mar 21, 2016 at 9:37 AM, Jordan Rose <jordan_rose@apple.com> wrote:

-1 for the signature change. The most common case of autoreleasepool does
not return a value and has a multi-statement body that prevents the result
type from being inferred. This needs to continue to work:

autoreleasepool {
  foo()
  bar()
}

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Chris Lattner) #7

I understand that Jordan withdrew his objection later (because no overload is required) but it still isn't clear to me that autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking like a statement, not an expression. Someday I hope it will be possible to break/continue/return/throw out of a closure, and at that point, it will look exactly like a statement.

The problem with adding a return value for this is that (so far) we don’t allow the same thing to ‘do’, ‘if’, ‘switch’ and other statements. I’d argue that autoreleasepool should follow as close as possible in do’s footsteps: if/when we decide to expressionize these statements, at that point should we expressionize autoreleasepool to match.

-Chris

···

On Mar 21, 2016, at 9:37 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

-1 for the signature change. The most common case of autoreleasepool does not return a value and has a multi-statement body that prevents the result type from being inferred. This needs to continue to work:

autoreleasepool {
  foo()
  bar()
}

If we had a rule to default generic return values to Void if they aren't used then I'd be okay with it, but that'd be a separate proposal. (Providing two overloads may also work; haven't tested it.)


(Dmitri Gribenko) #8

Oh, I see your point. Yes, you're right.

Dmitri

···

On Mon, Mar 21, 2016 at 7:56 AM, Timothy Wood <tjw@omnigroup.com> wrote:

What I meant was
whether it is possible for code outside stdlib to easily wrap a
autoreleasepool that does not have `rethrow` applied and make one that does.
Or, more generically, is it possible in Swift, given only `func f(@noescape
body: Void -> Void)` to write `func f(@noescape body: Void throws -> Void)
rethrows`? It seemed to me like it isn’t possible, since the wrapper needs
to capture the error inside a do/catch in a var and then optionally re-throw
it outside the do/catch, but the rethrow can’t be done (as far as I can see)
since that seems to make the compiler thing that the function should be
marked as `throws` instead of `rethrows`.

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Jordan Rose) #9

Um. Thank you for actually testing it! Objection withdrawn; changed to a +1. :slight_smile: Thanks, Dmitri, Tim. No overload needed.

Jordan

···

On Mar 21, 2016, at 11:43, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Mon, Mar 21, 2016 at 9:37 AM, Jordan Rose <jordan_rose@apple.com> wrote:

-1 for the signature change. The most common case of autoreleasepool does
not return a value and has a multi-statement body that prevents the result
type from being inferred. This needs to continue to work:

autoreleasepool {
foo()
bar()
}

It seems to work for me:

func poolPush() {}
func poolPop() {}

public func autoreleasepool<Result>(@noescape code: () throws ->
Result) rethrows -> Result {
poolPush()
defer { poolPop() }
return try code()
}

func foo() {}
func bar() {}

autoreleasepool {
foo()
bar()
}


(Timothy J. Wood) #10

If this is the plan, then I’d agree that adding a return value would not be good since it would make that “inlining” more of a breaking change. But, to some extent that ship has already sailed:

  func f() {
    autoreleasepool {
      if (somethingWithLotsOfAutoreleasedObjects()) {
        return;
      }
      somethingImportant()
    }
  }

If `autoreleasepool` is turned into a statement instead of a function, the meaning of existing code will silently change in possibly disastrous and hard to detect ways. Is there a plan in place to deal with this? The only idea that occurs to me would be to leave `autoreleasepool` as is for a transitional time, deprecated, and add a statement with a new name. Though, if a newly named statement were to be added, then we’re back to being able to leave `autoreleasepool` as a function with a return value and error =)

With the `rethrow`/`throws` annotation, the migration would be easier since the compiler would catch `try autorelease {...}` as invalid and the extra `try` could be removed.

Either way, I’m most interested in the `rethrow`/`throws` annotations since passing a value and error back out are most error-prone part, and since it isn’t possible to add in a wrapper. The return value would be possible to re-add in a wrapper, of course.

-tim

···

On Mar 23, 2016, at 10:36 PM, Chris Lattner <clattner@apple.com> wrote:
I understand that Jordan withdrew his objection later (because no overload is required) but it still isn't clear to me that autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking like a statement, not an expression. Someday I hope it will be possible to break/continue/return/throw out of a closure, and at that point, it will look exactly like a statement.

The problem with adding a return value for this is that (so far) we don’t allow the same thing to ‘do’, ‘if’, ‘switch’ and other statements. I’d argue that autoreleasepool should follow as close as possible in do’s footsteps: if/when we decide to expressionize these statements, at that point should we expressionize autoreleasepool to match.


(Dave Abrahams) #11

I guess there's no arguing with the intention here, because
autoreleasepool in the library predates me on the project, but it's
worth asking: why is it a good thing for autoreleasepool to be like a
statement and not like an expression? As far as I can tell that just
makes it less useful and more cumbersome. It is also an illusion that
breaks down at the edges, so why try to maintain it?

···

on Wed Mar 23 2016, Chris Lattner <swift-evolution@swift.org> wrote:

On Mar 21, 2016, at 9:37 AM, Jordan Rose via swift-evolution >> <swift-evolution@swift.org> wrote:

-1 for the signature change. The most common case of autoreleasepool
does not return a value and has a multi-statement body that prevents

the result type from being inferred. This needs to continue to work:

autoreleasepool {
  foo()
  bar()
}

If we had a rule to default generic return values to Void if they
aren't used then I'd be okay with it, but that'd be a separate
proposal. (Providing two overloads may also work; haven't tested
it.)

I understand that Jordan withdrew his objection later (because no
overload is required) but it still isn't clear to me that
autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking
like a statement, not an expression.

--
Dave


(Timothy Wood) #12

[...]

If `autoreleasepool` is turned into a statement instead of a function, the meaning of existing code will silently change in possibly disastrous and hard to detect ways. Is there a plan in place to deal with this? The only idea that occurs to me would be to leave `autoreleasepool` as is for a transitional time, deprecated, and add a statement with a new name. Though, if a newly named statement were to be added, then we’re back to being able to leave `autoreleasepool` as a function with a return value and error =)

[...]

Is there any further concern about leaving autoreleasepool() as a function? I’m not sure what the next step in this proposal should be -- should I note this concern in the proposal, or does it need to be resolved before there is further progress?

Thanks!

-tim

···

On Mar 24, 2016, at 8:37 AM, Timothy J. Wood via swift-evolution <swift-evolution@swift.org> wrote:


(Chris Lattner) #13

I understand that Jordan withdrew his objection later (because no overload is required) but it still isn't clear to me that autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking like a statement, not an expression. Someday I hope it will be possible to break/continue/return/throw out of a closure, and at that point, it will look exactly like a statement.

The problem with adding a return value for this is that (so far) we don’t allow the same thing to ‘do’, ‘if’, ‘switch’ and other statements. I’d argue that autoreleasepool should follow as close as possible in do’s footsteps: if/when we decide to expressionize these statements, at that point should we expressionize autoreleasepool to match.

If this is the plan, then I’d agree that adding a return value would not be good since it would make that “inlining” more of a breaking change. But, to some extent that ship has already sailed:

  func f() {
    autoreleasepool {
      if (somethingWithLotsOfAutoreleasedObjects()) {
        return;
      }
      somethingImportant()
    }
  }

If `autoreleasepool` is turned into a statement instead of a function, the meaning of existing code will silently change in possibly disastrous and hard to detect ways.

Hi Tim,

I don’t think I was clear with what I was suggesting. My point isn’t that such a change would be source breaking. My point is that making autoreleasepool returning a value would make it inconsistent with the rest of swift. If the analogy to statements doesn’t make sense, then maybe the analogy to sequence.forEach {} does:

Why shouldn’t the forEach algorithm return the result of the last execution of its closure argument (suitably wrapped in an optional so it can represent an empty sequence)? Clearly we could do this. Clearly it would be useful in certain narrow cases.

The reason is that Swift is not a pervasively expression oriented language. We may consider changing that in the future if we can find a design that makes sense, but until and if we do that, it is best for the language and library features to behave consistently, to reduce cognitive dissonance in developers.

Is there a plan in place to deal with this?

There are several discussions, but none of them are in scope for Swift 3. My recommendation is that you align with what we have in Swift 3, and if we do ever generalize the language and the rest of the stdlib, we can generalize autoreleasepool along with it.

-Chris

···

On Mar 24, 2016, at 8:37 AM, Timothy J. Wood <tjw@omnigroup.com> wrote:

On Mar 23, 2016, at 10:36 PM, Chris Lattner <clattner@apple.com> wrote:

The only idea that occurs to me would be to leave `autoreleasepool` as is for a transitional time, deprecated, and add a statement with a new name. Though, if a newly named statement were to be added, then we’re back to being able to leave `autoreleasepool` as a function with a return value and error =)

With the `rethrow`/`throws` annotation, the migration would be easier since the compiler would catch `try autorelease {...}` as invalid and the extra `try` could be removed.

Either way, I’m most interested in the `rethrow`/`throws` annotations since passing a value and error back out are most error-prone part, and since it isn’t possible to add in a wrapper. The return value would be possible to re-add in a wrapper, of course.

-tim


(Jordan Rose) #14

The weak link for me is the assertion that 'autoreleasepool' is more like 'forEach' than 'withCString' or 'withUnsafeMutablePointer'. We've seen some very nice simplifications by having those return values (usually replacing optional 'var' with a non-optional 'let').

Jordan

···

On Mar 29, 2016, at 9:54 , Chris Lattner <clattner@apple.com> wrote:

On Mar 24, 2016, at 8:37 AM, Timothy J. Wood <tjw@omnigroup.com> wrote:

On Mar 23, 2016, at 10:36 PM, Chris Lattner <clattner@apple.com> wrote:
I understand that Jordan withdrew his objection later (because no overload is required) but it still isn't clear to me that autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking like a statement, not an expression. Someday I hope it will be possible to break/continue/return/throw out of a closure, and at that point, it will look exactly like a statement.

The problem with adding a return value for this is that (so far) we don’t allow the same thing to ‘do’, ‘if’, ‘switch’ and other statements. I’d argue that autoreleasepool should follow as close as possible in do’s footsteps: if/when we decide to expressionize these statements, at that point should we expressionize autoreleasepool to match.

If this is the plan, then I’d agree that adding a return value would not be good since it would make that “inlining” more of a breaking change. But, to some extent that ship has already sailed:

  func f() {
    autoreleasepool {
      if (somethingWithLotsOfAutoreleasedObjects()) {
        return;
      }
      somethingImportant()
    }
  }

If `autoreleasepool` is turned into a statement instead of a function, the meaning of existing code will silently change in possibly disastrous and hard to detect ways.

Hi Tim,

I don’t think I was clear with what I was suggesting. My point isn’t that such a change would be source breaking. My point is that making autoreleasepool returning a value would make it inconsistent with the rest of swift. If the analogy to statements doesn’t make sense, then maybe the analogy to sequence.forEach {} does:

Why shouldn’t the forEach algorithm return the result of the last execution of its closure argument (suitably wrapped in an optional so it can represent an empty sequence)? Clearly we could do this. Clearly it would be useful in certain narrow cases.

The reason is that Swift is not a pervasively expression oriented language. We may consider changing that in the future if we can find a design that makes sense, but until and if we do that, it is best for the language and library features to behave consistently, to reduce cognitive dissonance in developers.

Is there a plan in place to deal with this?

There are several discussions, but none of them are in scope for Swift 3. My recommendation is that you align with what we have in Swift 3, and if we do ever generalize the language and the rest of the stdlib, we can generalize autoreleasepool along with it.


(Chris Lattner) #15

I’m not arguing against “utility” here. Perhaps the wedge is so strong for me, because @autoreleasepool *is* a statement in ObjC.

-Chris

···

On Mar 29, 2016, at 9:57 AM, Jordan Rose <jordan_rose@apple.com> wrote:

Is there a plan in place to deal with this?

There are several discussions, but none of them are in scope for Swift 3. My recommendation is that you align with what we have in Swift 3, and if we do ever generalize the language and the rest of the stdlib, we can generalize autoreleasepool along with it.

The weak link for me is the assertion that 'autoreleasepool' is more like 'forEach' than 'withCString' or 'withUnsafeMutablePointer'. We've seen some very nice simplifications by having those return values (usually replacing optional 'var' with a non-optional 'let').


(Joe Groff) #16

Yeah, I agree with Jordan, the analogy to `forEach` makes no sense, Chris. The standard library is overwhelmingly consistent in forwarding return values from its "scope-guard"-like functions which set up a thing, call a closure once, clean up the thing, then return. `autoreleasepool` is the odd one out.

-Joe

···

On Mar 29, 2016, at 9:57 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 29, 2016, at 9:54 , Chris Lattner <clattner@apple.com> wrote:

On Mar 24, 2016, at 8:37 AM, Timothy J. Wood <tjw@omnigroup.com> wrote:

On Mar 23, 2016, at 10:36 PM, Chris Lattner <clattner@apple.com> wrote:
I understand that Jordan withdrew his objection later (because no overload is required) but it still isn't clear to me that autoreleasepool should return a value.

Here’s my thought process: autoreleasepool is *intentionally* looking like a statement, not an expression. Someday I hope it will be possible to break/continue/return/throw out of a closure, and at that point, it will look exactly like a statement.

The problem with adding a return value for this is that (so far) we don’t allow the same thing to ‘do’, ‘if’, ‘switch’ and other statements. I’d argue that autoreleasepool should follow as close as possible in do’s footsteps: if/when we decide to expressionize these statements, at that point should we expressionize autoreleasepool to match.

If this is the plan, then I’d agree that adding a return value would not be good since it would make that “inlining” more of a breaking change. But, to some extent that ship has already sailed:

  func f() {
    autoreleasepool {
      if (somethingWithLotsOfAutoreleasedObjects()) {
        return;
      }
      somethingImportant()
    }
  }

If `autoreleasepool` is turned into a statement instead of a function, the meaning of existing code will silently change in possibly disastrous and hard to detect ways.

Hi Tim,

I don’t think I was clear with what I was suggesting. My point isn’t that such a change would be source breaking. My point is that making autoreleasepool returning a value would make it inconsistent with the rest of swift. If the analogy to statements doesn’t make sense, then maybe the analogy to sequence.forEach {} does:

Why shouldn’t the forEach algorithm return the result of the last execution of its closure argument (suitably wrapped in an optional so it can represent an empty sequence)? Clearly we could do this. Clearly it would be useful in certain narrow cases.

The reason is that Swift is not a pervasively expression oriented language. We may consider changing that in the future if we can find a design that makes sense, but until and if we do that, it is best for the language and library features to behave consistently, to reduce cognitive dissonance in developers.

Is there a plan in place to deal with this?

There are several discussions, but none of them are in scope for Swift 3. My recommendation is that you align with what we have in Swift 3, and if we do ever generalize the language and the rest of the stdlib, we can generalize autoreleasepool along with it.

The weak link for me is the assertion that 'autoreleasepool' is more like 'forEach' than 'withCString' or 'withUnsafeMutablePointer'. We've seen some very nice simplifications by having those return values (usually replacing optional 'var' with a non-optional 'let').


(Dave Abrahams) #17

+1; there's really no good reason to hobble autoreleasepool AFAICS.

···

on Tue Mar 29 2016, Joe Groff <swift-evolution@swift.org> wrote:

Yeah, I agree with Jordan, the analogy to `forEach` makes no sense,
Chris. The standard library is overwhelmingly consistent in forwarding
return values from its "scope-guard"-like functions which set up a
thing, call a closure once, clean up the thing, then
return. `autoreleasepool` is the odd one out.

--
Dave


(Russ Bishop) #18

One might go so far as to say that if/do/case/etc are the odd ducks in not being expressionized. :wink:

Russ

···

On Mar 29, 2016, at 10:02 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Yeah, I agree with Jordan, the analogy to `forEach` makes no sense, Chris. The standard library is overwhelmingly consistent in forwarding return values from its "scope-guard"-like functions which set up a thing, call a closure once, clean up the thing, then return. `autoreleasepool` is the odd one out.

-Joe


(Andrew Bennett) #19

I like this proposal, but I think I agree with Chris, the return value
seems inconsistent.

I am +1 on rethrows.

Your proposed autoreleasepool returns a (non discardable) value and/or
performs a side-effect, depending on how it is being used. Also, most other
standard library functions taking closures are methods that can be chained.

An interesting side-effect of this, not seen elsewhere:

    self.list.forEach { print($0) }

    self.list.dropLast() // warning: result unused

    self.list.removeLast() // no warning, the result is discardable

    // all warnings, or no warnings

    autoreleasepool { self.list.forEach { print($0) } }

    autoreleasepool { self.list.dropLast() }

    autoreleasepool { self.list.removeLast() }

Currently the second call provides the appropriate warning. After this
proposal either all or none of these calls will fire that warning
(depending on the annotation). In other cases this is overcome with
multiple functions; i.e. `forEach` versus `map`.

*Solutions and Alternatives*

*Void version*
You can provide a void version of the function, if you can get around the
ambiguous function resolution, but that doesn't help with `removeLast()`
(the following linked proposal helps).

Shameless plug, I've got a proposal that would solve these issues:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160328/013559.html

*Simplify current usage*
A proposal like this one could improve the current usage:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/008167.html

It would allow us to do things like this (note the `let`):

    let x: Int, y: Float, z: Double

    autoreleasepool {

        (x, y) = calculateSomeStuff()

        z = calculateMoreStuff(x, y, 123)

    }

It would work with `@discardable`, and the function signature would be
consistent with it having side-effects.

*Discardable annotation*
Maybe you can do this in the future (similar has been discussed, I forget
the details):

    autoreleasepool { () @discardable -> Int in ... }

*New function, different name*
If the current proposal did go ahead then I think the old function should
stay around and your function should have a new name, because of the above
reasons. Something like autoreleasepoolvalue although that's quite long.


(Chris Lattner) #20

Ok, it sounds like there is support for the return value. Tim, feel free to include it, but mention that it is controversial in the “alternatives considered” section. The community can hash it out in the review period, thanks!

-Chris

···

On Mar 29, 2016, at 4:18 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Tue Mar 29 2016, Joe Groff <swift-evolution@swift.org> wrote:

Yeah, I agree with Jordan, the analogy to `forEach` makes no sense,
Chris. The standard library is overwhelmingly consistent in forwarding
return values from its "scope-guard"-like functions which set up a
thing, call a closure once, clean up the thing, then
return. `autoreleasepool` is the odd one out.

+1; there's really no good reason to hobble autoreleasepool AFAICS.