Passing 'consuming sending' argument to a non-escaping closure

AFAIK there is no way to express a consuming capture for a closure. So when we need to pass a consuming argument we have to use a workaround. For example

func f(_ v: consuming some ~Copyable) {}
func g(_ v: consuming some ~Copyable) { // Error: Missing reinitialization of closure capture 'v' after consume
  withExtendedLifetime(()) { // any non-escaping closure
    f(v)
  }
}

becomes

func f(_ v: consuming some ~Copyable) {}
func g(_ v: consuming some ~Copyable) {
  var v = Optional(v)
  withExtendedLifetime(()) {
    f(v.take()!)
  }
}

However, this workaround doesn't compose well with the sending keyword.

func f(_ v: consuming sending some ~Copyable) {}
func g(_ v: consuming sending some ~Copyable) {
  var v = Optional(v)
  withExtendedLifetime(()) {
    f(v.take()!) // Task-isolated value of type 'some ~Copyable' passed as a strongly transferred parameter; later accesses could race; 
  }
}

I wonder Is there a better workaround here?
Thanks in advance

You're correct - consuming captures don't work and they shouldn't until the language can express something like a call-once closure. The Optional value you're wrapping this in provides a kind of poor runtime-based simulation of the feature you need by pushing the implication of invoking the closure more than once into becoming a runtime crash. AFAIK, what you have is the best we can do until that time.

4 Likes

Well, the above is still true, but have fun with this

struct CallOnce<A: ~Copyable, 
                R: ~Copyable,
                Captures: ~Copyable>: ~Copyable {
    typealias FnType = (consuming A, consuming Captures) -> R
    let fn: FnType
    let captures: Captures
    init(_ captures: consuming Captures, _ fn: @escaping FnType) {
      self.fn = fn
      self.captures = captures
    }
    consuming func callAsFunction(_ a: consuming A) -> R {
        return fn(a, captures)
    }
}

do {
    var hello = 1
    let callOnce = CallOnce(hello) { 
        (a: consuming Void, c: consuming Int) in return c
    }
    callOnce(())
    // callOnce(())
}
4 Likes

I agree with you, my personal observation is that consuming and sending does not compose well.

To make your last code snippet work, this is my best effort.

func f(_ v: consuming sending some ~Copyable) {}
func g(_ v: consuming sending some ~Copyable) {
  var v = Optional(v)
  withExtendedLifetime(()) {
    nonisolated(unsafe) let value = v.take()!
    f(value)
  }
}

Hope guys can show me a more elegant way...

1 Like