Capturing a Struct Reference in a Closure Doesn't Allow Mutations to Occur

Closure cannot implicitly capture a mutating self parameter

The short version

The type owning your call to FirebaseRef.observeSingleEvent(of:with:) is most likely a value type (a struct?), in which case a mutating context may not explicitly capture self in an @escaping closure.

The simple solution is to update your owning type to a reference once (class).


The longer version

The observeSingleEvent(of:with:) method of Firebase is declared as follows

func observeSingleEvent(of eventType: FIRDataEventType, 
with block: @escaping (FIRDataSnapshot) -> Void)

The block closure is marked with the @escaping parameter attribute, which means it may escape the body of its function, and even the lifetime of self (in your context). Using this knowledge, we construct a more minimal example which we may analyze:

struct Foo {
private func bar(with block: @escaping () -> ()) { block() }

mutating func bax() {
bar { print(self) } // this closure may outlive 'self'
/* error: closure cannot implicitly capture a
mutating self parameter */
}
}

Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:

  • SE-0035: Limiting inout capture to @noescape contexts

Stating [emphasis mine]:

Capturing an inout parameter, including self in a mutating
method
, becomes an error in an escapable closure literal, unless the
capture is made explicit
(and thereby immutable).

Now, this is a key point. For a value type (e.g. struct), which I believe is also the case for the type that owns the call to observeSingleEvent(...) in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).

The simplest solution to this issue would be making the type owning the observeSingleEvent(...) a reference type, e.g. a class, rather than a struct:

class Foo {
init() {}
private func bar(with block: @escaping () -> ()) { block() }

func bax() {
bar { print(self) }
}
}

Just beware that this will capture self by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self weakly, e.g.

FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...

Why is declaring self not required in structures where it's required in classes?

The purpose of including self when using properties inside an escaping closure (whether optional closure or one explicitly marked as @escaping) with reference types is to make the capture semantics explicit. As the compiler warns us if we remove self reference:

Reference to property 'someProperty' in closure requires explicit use of 'self' to make capture semantics explicit.

But there are no ambiguous capture semantics with structs. You are always dealing with a copy inside the escaping closure. It is only ambiguous with reference types, where you need self to make clear where the strong reference cycle might be introduced, which instance you are referencing, etc.


By the way, with class types, referencing self in conjunction with the property is not the only way to make the capture semantics explicit. For example, you can make your intent explicit with a “capture list”, either:

  • Capture the property only:

     class SomeClass {
    var someProperty = 1

    func someMethod(completion: @escaping () -> Void) { ... }

    func anotherMethod() {
    someMethod { [someProperty] in // this captures the property, but not `self`
    print(someProperty)
    }
    }
    }
  • Or capture self:

     class SomeClass {
    var someProperty = 1

    func someMethod(completion: @escaping () -> Void) { ... }

    func anotherMethod() {
    someMethod { [self] in // this explicitly captures `self`
    print(someProperty)
    }
    }
    }

Both of these approaches also make it explicit what you are capturing.

Passing a local mutable struct into an async let binding

As SE-0317 - async let bindings says:

... async let is similar to a let, in that it defines a local constant that is initialized by the expression on the right-hand side of the =. However, it differs in that the initializer expression is evaluated in a separate, concurrently-executing child task.

The child task begins running as soon as the async let is encountered.

...

A async let creates a child-task, which inherits its parent task's priority as well as task-local values. Semantically, this is equivalent to creating a one-off TaskGroup which spawns a single task and returns its result ...

Similarly to the [group.addTask] function, the closure is @Sendable and nonisolated, meaning that it cannot access non-sendable state of the enclosing context. For example, it will result in a compile-time error, preventing a potential race condition, for a async let initializer to attempt mutating a closed-over variable:

var localText: [String] = ...
async let w = localText.removeLast() // error: mutation of captured var 'localText' in concurrently-executing code

The async let initializer may refer to any sendable state, same as any non-isolated sendable closure.

So, it is not the case that the parameter to data(for:delegate:) is copied and then the asynchronous task is created, but rather the other way around.

Usually, if you were using a closure, you would just add request to the closure’s capture list, but that’s not possible in this case. E.g., you could create a Task yourself with a capture list, achieving something akin to async let, but with greater control:

func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example

let task = Task { [request] in
try await URLSession.shared.data(for: request).0
}

// do some more stuff in parallel

print("Response body: \(String(data: try await task.value, encoding: .utf8) ?? "Not string")")
}

Obviously, you can simply await the data(for:delegate:), rather than async let, and the problem goes away:

func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example

let data = try await URLSession.shared.data(for: request).0

print("Response body: \(String(data: data, encoding: .utf8) ?? "Not string")")
}

Save struct in background mutating function

I think it will help if we minimally elicit the error message you're getting. (For delay, see dispatch_after - GCD in swift?.)

struct S {
var name = ""
mutating func test() {
delay(1) {
self.name = "Matt" // Error: Closure cannot ...
// ... implicitly capture a mutating self parameter
}
}
}

The reason lies in the peculiar nature of struct (and enum) mutation: namely, it doesn't really exist. When you set a property of a struct, what you're really doing is copying the struct instance and replacing it with another. That is why only a var-referenced struct instance can be mutated: the reference must be replaceable in order for the instance to be mutable.

Now we can see what's wrong with our code. Obviously it is legal for a mutating method to mutate self; that is what mutating means. But in this case we are offering to go away for a while and then suddenly reappear on the scene (after 1 second, in this case) and now mutate self. So we are going to maintain a copy of self until some disconnected moment in the future, when self will suddenly be somehow replaced. That is incoherent, not least because who knows how the original self may have been mutated in the meantime, rendering our copy imperfect; and the compiler prevents it.

The same issue does not arise with a nonescaping closure:

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

struct S {
var name = ""
mutating func test() {
f {
self.name = "Matt" // fine
}
}
}

That's because the closure is nonescaping; it is executed now, so the incoherency about what will happen in the future is absent. This is an important difference between escaping and nonescaping closures, and is one of the reasons why they are differentiated.

Also, the same issue does not arise with a class:

class C {
var name = ""
func test() {
delay(1) {
self.name = "Matt" // fine
}
}
}

That's because the class instance is captured by reference in the closure, and a class instance is mutable in place.

(See also my little essay here: https://stackoverflow.com/a/27366050/341994.)

mutation reference for Box closure, but not live long enough

To understand what is happening here you'll need to make a little dive into the Rust Book and learn about lifetimes.

I'll simplify for sake of explanation and comment this:

//type Executor = Box< dyn FnMut() -> ()>;

And change the signature of exec function to this:

fn exec(&self, mut executor: Box<dyn FnMut() -> () >) {
executor();
}

First of all let's notice that if we will comment very last line:

//f_test.exec(executor);

the error is gone. The reason for this is that reference to the closure you created - &Box<dyn FnMut() -> ()> has shorter lifetime than other part of main (it's scoped). It means that when you are creating this closure it exists, but just after the reference to this is dropped. That's why it doesn't complain if you not use it, because it's all okay.

Lifetimes are not variables, they are claims, kinda of similar to generics.
If you would change right now the signature of function exec function to this:

impl Ftest {
fn exec<'a>(&self, mut executor: Box<dyn FnMut() -> () + 'a>) {
executor();
}
}

I would say this:

  • let's say that lifetime of my function is a,
  • it would takes some parameters and return Box of function which lifetime would be also a, so it would live as long as I.

By doing it we hinted compiler that the lifetime of closure you'll create should live "in same lifetime" as exec() is executed. That's why even if we bring back the last line of main it would work :D.

There is also an option of adding move keyword:

fn main() {
let mut closure_test = ClosureTest::new(5);
let f_test = Ftest {};
let executor = Box::new(move || {
receive_test(&mut closure_test);
});

//println!("{}", closure_test.x);
f_test.exec(executor);
}

This will work, because by adding move keyword we actually tell that we would rather move closure_test object to our scope, not just a reference, therefore it's not dropped. However you wouldn't be able to use it later as shown if you'll uncomment the line with println!().

I'll be happy if anybody would provide some corrections and more details. This concept in Rust is unique and takes time to fully understand so it would be helpful.

Mutable vs. immutable borrows in closure?

If you really want to do it with a closure, you will have to pass the vector by parameter:

let next = |v: &Vec<_>, i| (i + 1) % v.len();

This makes the closure borrow per-call, rather than capture for the scope. You still need to separate the borrows, though:

let j = next(&v, 1usize);
v[j] = 1;

To make your life easier, you can put everything inside the closure instead:

let next = |v: &mut Vec<_>, i, x| {
let j = (i + 1) % v.len();
v[j] = x;
};

Which allows you to simply do:

next(&mut v, 1usize, 1);
next(&mut v, 2usize, 2);
// etc.

This pattern is useful for cases where you are writing a closure just for avoiding local code repetition (which I suspect is why you are asking given the comments).

Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value

Using an inout parameter exclusively for an asynchronous task is an abuse of inout – as when calling the function, the caller's value that is passed into the inout parameter will not be changed.

This is because inout isn't a pass-by-reference, it's just a mutable shadow copy of the parameter that's written back to the caller when the function exits – and because an asynchronous function exits immediately, no changes will be written back.

You can see this in the following Swift 2 example, where an inout parameter is allowed to be captured by an escaping closure:

func foo(inout val: String, completion: (String) -> Void) {
dispatch_async(dispatch_get_main_queue()) {
val += "foo"
completion(val)
}
}

var str = "bar"
foo(&str) {
print($0) // barfoo
print(str) // bar
}
print(str) // bar

Because the closure that is passed to dispatch_async escapes the lifetime of the function foo, any changes it makes to val aren't written back to the caller's str – the change is only observable from being passed into the completion function.

In Swift 3, inout parameters are no longer allowed to be captured by @escaping closures, which eliminates the confusion of expecting a pass-by-reference. Instead you have to capture the parameter by copying it, by adding it to the closure's capture list:

func foo(val: inout String, completion: @escaping (String) -> Void) {
DispatchQueue.main.async {[val] in // copies val
var val = val // mutable copy of val
val += "foo"
completion(val)
}
// mutate val here, otherwise there's no point in it being inout
}

(Edit: Since posting this answer, inout parameters can now be compiled as a pass-by-reference, which can be seen by looking at the SIL or IR emitted. However you are unable to treat them as such due to the fact that there's no guarantee whatsoever that the caller's value will remain valid after the function call.)


However, in your case there's simply no need for an inout. You just need to append the resultant array from your request to the current array of results that you pass to each request.

For example:

fileprivate func collectAllAvailable(_ storage: [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) {
if let client = self.client {
let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in

guard error == nil else {
completion(nil, error)
return
}

guard let resultCollection = resultCollection, let results = resultCollection.results else {
completion(nil, NSError.unhandledError(ResultCollection.self))
return
}

let storage = storage + results // copy storage, with results appended onto it.
if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {
self.collectAllAvailable(storage, nextUrl: nextUrlItr, completion: completion)
} else {
completion(storage, nil)
}
}
} else {
completion(nil, NSError.unhandledError(ResultCollection.self))
}
}


Related Topics



Leave a reply



Submit