Closure
When a function or lambda expression captures a variable from the static scope that defines it, the function or lambda expression and the captured variable form a closure. The closure can run properly even if it is out of the scope where the closure definition is located.
Variable capture refers to any of the following scenarios where a variable is accessed in the definition of a function or lambda expression:
-
A local variable defined outside a function is accessed in the default value of a parameter in the function.
-
A local variable defined outside a function or lambda expression is accessed in the function or lambda expression.
-
An instance member variable or
this
is accessed in a function or lambda expression, which is defined insideclass
orstruct
and is not a member function.
Variable access in the following cases is not variable capture:
-
A local variable defined inside a function or lambda expression is accessed in the function or lambda expression.
-
A parameter of a function or lambda expression is accessed in the function or lambda expression.
-
A global variable or static member variable is accessed.
-
An instance member variable is accessed in an instance member function or attribute. Because the instance member function or attribute passes
this
as a parameter, all instance member variables are accessed throughthis
in the instance member function or attribute.
A variable is captured when a closure is defined. Therefore, the rules for capturing a variable are as follows:
-
A captured variable must be visible when a closure is defined. Otherwise, an error is reported during compilation.
-
A captured variable must have been initialized when a closure is defined. Otherwise, an error is reported during compilation.
Example 1: The add
closure captures the num
local variable declared by let
and returns a value outside the scope defined by num
. When add
is called, num
can still be accessed.
func returnAddNum(): (Int64) -> Int64 {
let num: Int64 = 10
func add(a: Int64) {
return a + num
}
add
}
main() {
let f = returnAddNum()
println(f(10))
}
The result is as follows:
20
Example 2: A captured variable must be visible when a closure is defined.
func f() {
let x = 99
func f1() {
println(x)
}
let f2 = { =>
println(y) // Error, cannot capture 'y' which is not defined yet
}
let y = 88
f1() // Print 99.
f2()
}
Example 3: A captured variable must have been initialized before a closure is defined.
func f() {
let x: Int64
func f1() {
println(x) // Error, x is not initialized yet.
}
x = 99
f1()
}
If the captured variable is of the reference type, you can change the value of its mutable instance member variable.
class C {
public var num: Int64 = 0
}
func returnIncrementer(): () -> Unit {
let c: C = C()
func incrementer() {
c.num++
}
incrementer
}
main() {
let f = returnIncrementer()
f() // c.num increases by 1
}
Closures that capture variables declared by var
can only be called and cannot be used as first-class citizens to prevent escaping closures, including not being assigned to variables, not being used as arguments or return values, and the names of these closures not being directly used as expressions.
func f() {
var x = 1
let y = 2
func g() {
println(x) // OK, captured a mutable variable.
}
let b = g // Error, g cannot be assigned to a variable
g // Error, g cannot be used as an expression
g() // OK, g can be invoked
g // Error, g cannot be used as a return value.
}
Note that capturing is transitive. If the f
function calls the g
function that captures the var
variable which is not defined inside the f
function, the f
function also captures the var
variable and cannot be used as a first-class citizen either.
In the following example, g
captures the x
variable declared by var
, f
calls g
, and x
captured by g
is not defined inside f
. In this case, f
cannot be used as a first-class citizen either.
func h(){
var x = 1
func g() { x } // captured a mutable variable
func f() {
g() // invoked g
}
return f // Error
}
In the following example, g
captures the x
variable declared by var
, and f
calls g
. However, x
captured by g
is defined inside f
, and f
does not capture other variables declared by var
. Therefore, f
is still used as a first-class citizen.
func h(){
func f() {
var x = 1
func g() { x } // captured a mutable variable
g()
}
return f // Ok
}
Accessing a static member variable or global variable is not a variable capture. Therefore, a global variable, a function of a static member variable, or a lambda expression modified by var
can still be used as a first-class citizen.
class C {
static public var a: Int32 = 0
static public func foo() {
a++ // OK
return a
}
}
var globalV1 = 0
func countGlobalV1() {
globalV1++
C.a = 99
let g = C.foo // OK
}
func g(){
let f = countGlobalV1 // OK
f()
}