Indexing into a RandomAccessCollection from a closure consumes it?

I'm trying to write a generic median function. As finding a median involves sorting the collection, but I don't want to copy the collection upfront, I sort the indices instead, and then fetch the median.

But this does not work: accessing the collection from the closure consumes it. Adding s to the capture list is still consumption.

func median<T>(_ s: borrowing T,
               averagingWith avgFcn: (T.Element, T.Element) -> T.Element)
               // Comparable doesn't necessarily have an averaging function
     -> T.Element?
       where T : RandomAccessCollection, T.Element : Comparable {
  switch s.count {
    // cases where the sort isn't necessary
    case 1:
      return s.first!
    case 2:
      return avgFcn(s.first!, s.last!)

    // Have multiple, will sort
    case 3...:
      let idx = s.indices.sorted(by: { s[$0] < s[$1] })
//    error: 's' is borrowed and cannot be consumed
//                                   `- note: consumed here
      let (quotient: mid, remainder: rem) = idx.count.quotientAndRemainder(dividingBy:2)
      let midlarger = s[idx[idx.indices[mid]]]
      if (rem != 0) { // odd number of elements
        return midlarger
      } else { // even number of elements...
        let midsmaller = s[idx[idx.indices[mid-1]]]
        if (midlarger == midsmaller) { // but don't call average just yet
          return midlarger
        } else {
          return avgFcn(midsmaller, midlarger)
        }
      }
    default:
      return nil // 0 elements
  }
}

Any idea how to make it work?

EDIT: oops forgot Swift's index is starts from 0

There's a few problems here. Explicitly marking a parameter disables implicit copying for that parameter. Since captures are by value, that makes capturing s in the closure a consume instead. Also, indices is allowed to capture it's collection (and actually does so for some existing collections).

The easiest way to fix this is to remove the explicit borrowing and just let the compiler insert copies where it needs to.

If you really need to avoid the ARC traffic though, the only way to do this is to create a custom sort function that borrows the collection and returns a sorted array of indices. I do not recommend this.

Captures aren’t by value, though, not unless explicitly enumerated. I don’t know if there’s syntax to do the right thing yet, but in theory none should be needed for a non-escaping closure specifically. (But indices potentially capturing is correct.)

1 Like