Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to denote mutable parameters in closures with Swift > 2.2?

Perhaps this is an Xcode 8 beta issue, however, prior to 2.2 the var keyword is allowed to prepend parameters in function signatures:

func (var stringName: String) { ... }

This is has since been deprecated in lieu of there being little benefit over inout

func (stringName: inout String) { ... }

I've attempted the following in a map closure, and though I don't receive a deprecation warning as mildly expected I should, the error was rather a segmentation fault: 11

let demoString = ["hi", "there", "world"].map { (var word) -> String in 
    let firstChar = word.remove(at: word.startIndex)
}

The error kicks in as soon as I attempt to mutate the (assumedly mutable) word variable.

I've attempted other variation e.g. using inout

let demoString = ["hi", "there", "world"].map { (word: inout String) -> String in 
    let firstChar = word.remove(at: word.startIndex)
}

But the compiler complains that this erroneously changes the signature of the closure altogether and won't compile.

Obviously, the workaround is simply to copy the variable to a local one within the closure:

let demoString = ["hi", "there", "world"].map { (word) -> String in 
    let tempWord = word
    let firstChar = tempWord.remove(at: tempWord.startIndex)
}

However, I am interested in knowing if this is expected functionality & whether or not there is a way of mutating a parameter passed into a closure directly?

like image 922
Kyle G Avatar asked Aug 11 '16 22:08

Kyle G


1 Answers

Closures can mutate inout arguments just fine:

var str1 = "Pine"
var str2 = "Juniper"

let closure = { (s1: inout String, s2: inout String) -> Void in
    s1 = "Oak"
    s2 = "Chestnut"
}

closure(&str1, &str2)

print(str1, str2)

The problem you are facing is Array.map doesn't have a method signature that includes an inout parameter.

The only way around this that I can think of is to extend Array and add the map method yourself:

extension Array {
    func map<T>(_ closure: (inout T) -> Void) -> Array<T> {
        var arr = [T]()
        for i in 0..<self.count {
            var temp : T = self[i] as! T
            closure(&temp)
            arr.append(temp)
        }
        return arr
    }
}

var hi = "hi", there = "there", world = "world"
var demoStrings = [hi, there, world]
var result = demoStrings.map { (word: inout String) in 
    word.remove(at: word.startIndex)
}

print(result) // ["i", "here", "orld"]
like image 80
Lightbeard Avatar answered Oct 04 '22 17:10

Lightbeard