Why is the `{}` syntax called `closure expression` in swift?

Closure is essentially a function that captures external environment variables.
However, the {} syntax is just a lambda expression.

I understand that it's easy to create a closure, but also be created by using the func keyword.

Advanced Swift points out as follows:

Sometimes people new to the topic of closures come at it in reverse order and maybe miss one of these points, or they conflate the terms closure and closure expression

My question is below.

  1. why {} syntax was named closure expression
  2. why closure expression gets shortened to closures in the plural form.

According to Closures chapter from this book

Closure expression is an unnamed closure, as opposed to named closure, which is function.

The closure expression syntax looks like this

{ [capture list] (parameters) -> returnType in statements }

While the function syntax looks like this

attributes func funcName(parameters) -> returnType {
  statements
}

Also, from the same book, plural form of closure expression is closure expressions.

1 Like

From a purely theoretical perspective, the only lambdas which Swift models are closures.

1 Like

Strictly speaking, neither the {} or a local func create closures on their own. By themselves, func merely declares a named function, and {} declares an anonymous function. Closures are created at a use site when a function is referenced as a function value. This means that direct applications of the function never allocate a closure, because no closure semantically exists:

func useClosure(_: () -> ()) {}

func foo() {
  var a = 0

  func bar() {
    print(a)
  }

  bar() // Does not allocate a closure because `bar` is directly applied

  _ = {
    print(a)
  }() // does not allocate a closure because the literal is directly applied

  useClosure(bar) // Closure is allocated to pass `bar` with the local value of `a` here
  useClosure { print(a) } // Closure is allocated to pass anonymous function here
}

(Whether the closure formed is a nonescaping stack closure or heap-allocated escaping closure is also determined by the call site, not fundamentally by the function declaration.)

So maybe a more technically appropriate term would be "anonymous function expression", but that's a mouthful, and in the most common use cases, you're using a function literal to pass a closure to another function, so "closure expression" took hold as the jargon term.

17 Likes

@Nevin @Lantua

Thanks for your reply.

I'm not a native English speaker.

In Advanced swift:

... a shorthand { } syntax called a closure expression.
Sometimes this gets shortened to “closures,”

I thought "closures" here is a plural forms.
But is this just an abbreviation for closure expression?

@Joe_Groff
Thank you for your answer.

This means that direct applications of the function never allocate a closure, because no closure semantically exists:

I missed the point of view whether it is a closure depends on the use site.
There is strictly no syntax for creating closures on this point, certainly.

... "closure expression" took hold as the jargon term.

That makes sense.
I understand that Swift distinguish "closure expression" and "closue", but the "{}" syntax can be called "closure expression" or "closures".

It depends of course, maybe you want or need to draw a line between an expression and an object (the closure). But otherwise in most cases you can safely use both terms interchangeably and be sure to be understood from a Swift user perspective.

1 Like

There is a minor grammatical error in the text you’re quoting—it should say either “closure expressions”/“closures” or “closure expression”/“closure”. This sort of mistake is so common that native English speakers barely notice it, but it must be terribly confusing for readers whose native language does not include plurals.

2 Likes

Joe,

You may have mixed up the operational semantics of closure and the
implementation details?

In the example you give, bar is a closure by, for example, the
definition given on Wikipedia,

    Operationally, a closure is a record storing a function together
    with an environment.

which looks like the same definition that I was taught in school many
years ago.

bar is the record, { print (a) } is the function, and var a = 0 is
the environment.

In your example, it is reasonable to hope that the Swift compiler does
not ever reserve any concrete storage for the record bar, at least not
if optimization is enabled. But that does not make bar any less of a
closure to my mind.

Dave

2 Likes

It's a bit splitting hairs, but no closure record is formed by the compiler until a call site is seen. While this can be seen as merely an optimization for local functions in isolation, it's really necessary for it to work this way for mutually-recursive local functions to work practically with the reference counting and strict initialization semantics Swift requires. If you have:

func outer(x: inout T) {
  var y = 0
  func foo() {
    print(x)
    bar()
  }
  func bar() {
    print(y)
    foo()
  }

  bar()
}

Then for foo and bar to be able to call each other, it wouldn't work out if the foo or bar declarations immediately formed a closure record at the declaration site, because they need to be able to access each other's captured variables x and y. If foo captured only x at its declaration site, then bar captured only y at its call site, neither would have enough context to invoke the other. And if you did this in the somewhat more traditional way of having foo and bar capture each other's closures, you would have both a reference cycle, causing a leak if naively implemented with reference counting, and a circular initialization problem building the mutually-recursive closure objects. Furthermore, access to the inout argument x must be restricted to nonescaping call sites in order not to violate the lifetime restriction on x. To maximize the expressivity of local functions and closures, and also coincidentally optimize immediate call sites and recursive local functions to reduce the number of closure allocations necessary, it turns out to be simpler to defer the closure formation for local functions to their actual use site—when you invoke bar immediately, or pass it as a function value, only then does the compiler crawl through its transitive dependencies to figure out what context needs to be captured along with the function to call it.

11 Likes
Terms of Service

Privacy Policy

Cookie Policy