Avoid a "variable was written to, but never read" without unnecessary read statements

I have a Combine publisher-returning functions, and I wanted to provide an overload with a callback. My understanding that this is a common way to achieve this:

func loadImage() -> AnyPublisher<UIImage, Error> {
   //...
}

func loadImage(completion: @escaping (UIImage) -> Void) {
   var c: AnyCancellable? = nil
   c = loadImage().sink(receiveCompletion: { _ in
      c = nil
   }, receiveValue: {
      completion($0)
   })
}

The closure temporarily retains a reference to the AnyCancellable until it completes.

This however causes a warning:

var c: AnyCancellable? = nil // Variable 'c' was written to, but never read

Is there a clean way to avoid this without adding unnecessary read statements?

Normally, you can just do _ = c, but there's also a problem that you need to ensure that c is still alive by the completion event.

If you're not reading from c, the compiler can assume that c isn't being used, and optimize it out. That'd be bad since you're retaining c to maintain the publisher subscription.

Use withExtendedLifetime on the last access to ensure that c is still alive by then. That last access should be in the completion block.

Could be either:

c = withExtendedLifetime(c) { nil }

// or

withExtendedLifetime(c) {}
c = nil
6 Likes

Thanks. I've never encountered withExtendedLifetime and still don't fully get this usage (i.e. shouldn't the entire thing be inside its closure?)

But, can you clarify - is _ = c enough as the last statement, or would the compiler still optimize it away and withExtendedLifetime the recommended approach?

The deal with withExtendedLifetime is that, x is guaranteed to be alive until the body completes its execution, even if body does not use x at all.

Now, what we want is that c is alive until completion is called. So we want to have withExtendedLifetime(c) { ... }, but we also want to mutate c to nil.

Since the x parameter is read-only, setting nil inside the body will violate the exclusivity rule:

withExtendedLifetime(c) {
  // Don't do this.
  c = nil 
}

we instead mutate c after withExtendedLifetime.

Honestly, I don't know.

1 Like

Thanks! Looks like your linked question got a pretty authoritative answer.

I don't think this is an exclusivity violation. withExtendedLifetime doesn't use inout, so passing c to it doesn't create a long-term access that the access in the closure could conflict with.

:thinking: You are right. The read access to argument c would end once we're inside the function. I shoulda checked first...

or use

var subscriptions = Set<AnyCancellable>()

loadImage().sink(receiveCompletion: { _ in
      c = nil
   }, receiveValue: {
      completion($0)
   })
.store(in: &subscriptions)