3

I can have a function to swap the first two elements of an array:

func swapFirstTwo(array: inout [Int]) {
  if array.count >= 2 {
    array.swapAt(0, 1)
  }
}

typealias Swapper = (inout [Int]) -> ()

// And I can have a variable = the function
let swapThem: Swapper = swapFirstTwo

// And it works like this:
var array = [1,2,3]
print(array)
swapThem(&array)
print(array)

// But I'm allergic to Global functions!
// It would be more swifty to have:

extension Array where Element == Int {
  mutating func swapFirstTwo2() {
    if count >= 2 {
      swapAt(0, 1)
    }
  }
}

typealias Swapper2 = (inout [Int]) -> () -> ()

// But when I do this:
let swapThemAgain: Swapper2 = Array.swapFirstTwo2
// I get the error:
// Partial application of 'mutating' method is not allowed; calling the function has undefined behavior and will be an error in future Swift versions

var array2 = [1,2,3]
print(array2)
array2.swapFirstTwo2()
print(array2)
// This in fact works but I've tried similar things and sometimes they appear to be unstable.
// How can I achieve doing: array2.swapFirstTwo2() without getting the error?

This in fact works but I've tried similar things and sometimes they appear to be unstable. Also the compiler warning needs to be heeded. How can I achieve doing: array2.swapFirstTwo2() without getting the warning/error?

Cortado-J
  • 2,035
  • 2
  • 20
  • 32
  • Interesting. I understand why partial application of a *bound* method wouldn't work, that would in-effect make all value types into reference types. But an unbound function like this should be fine, from what I can tell. – Alexander Nov 01 '18 at 23:48
  • 2
    @Alexander The problem is that an `inout` argument is only valid for the duration of the call, so applying `(inout [Int]) -> () -> ()` with `&array2` would give you a `() -> ()` that now has an invalid reference to `array2`. This problem will be fixed [if/when unapplied instance methods get flat signatures](https://github.com/apple/swift-evolution/blob/master/proposals/0042-flatten-method-types.md). – Hamish Nov 02 '18 at 00:21
  • @Hamish Ahhhh, brilliant! Just as I read your second sentence, I was thinking to myself "ah, so that problem will be solved with the new instance method types", and then boom, you mention exactly that – Alexander Nov 02 '18 at 02:36

1 Answers1

3

The reason why you get a warning (and soon to be an error in Swift 5 mode) on:

extension Array where Element == Int { 
  mutating func swapFirstTwo2() {
    if count >= 2 {
      swapAt(0, 1)
    }
  }
}

typealias Swapper2 = (inout [Int]) -> () -> ()
let swapThemAgain: Swapper2 = Array.swapFirstTwo2

is due to the fact that inout arguments are only valid for the duration of the call they're passed to, and therefore cannot be partially applied.

So if you were to call the returned (inout [Int]) -> () -> () with &array2, you would get back a () -> () which now has an invalid reference to array2. Attempting to call that function will yield undefined behaviour, as you're trying to mutate an inout argument outside of the window where it's valid.

This problem will be fixed if/when unapplied instance methods get flat signatures, as Array.swapFirstTwo2 will instead evaluate to a (inout [Int]) -> ().

But in the mean time, you can workaround the issue by using a closure instead:

typealias Swapper2 = (inout [Int]) -> ()
let swapThemAgain: Swapper2 = { $0.swapFirstTwo2() }

var array2 = [1,2,3]
print(array2) // [1, 2, 3]
array2.swapFirstTwo2()
print(array2) // [2, 1, 3]
Hamish
  • 78,605
  • 19
  • 187
  • 280