Cannot assign through subscript: 'dict' is a 'let' constant

Viewed 13658

I have a very simple problem with Swift. I created this function:

var dictAges : [String: Int] = ["John":40, "Michael":20, "Bob": -16]

func correctAges(dict:[String:Int]) {
     for (name, age) in dict {
          guard age >= 0 else {
          dict[name] = 0
          continue
          }
     }
}
correctAges(dict:dictAges)

But I don't understand the error:

"cannot assign through subscript: 'dict' is a 'let' constant, dict[name] = 0"

How can I solve it?

3 Answers

Input arguments to functions are immutable inside the function body and a Dictionary is a value type, so the dict you see inside the function is actually a copy of the original dictAges. When you call a function with a value type as its input argument, that input argument is passed by value, not passed by reference, hence inside the function you cannot access the original variable.

Either declare the dict input argument as inout or if you'd prefer the more functional approach, return a mutated version of the dictionary from the function.

Functional approach:

var dictAges : [String: Int] = ["John":40, "Michael":20, "Bob": -16]

func correctAges(dict:[String:Int])->[String:Int] {
     var mutatedDict = dict
     for (name, age) in mutatedDict {
          guard age >= 0 else {
              mutatedDict[name] = 0
              continue
          }
     }
     return mutatedDict
}
let newDict = correctAges(dict:dictAges) //["Michael": 20, "Bob": 0, "John": 40]

Inout version:

func correctAges(dict: inout [String:Int]){
    for (name,age) in dict {
        guard age >= 0 else {
            dict[name] = 0
            continue
        }
    }
}

correctAges(dict: &dictAges) //["Michael": 20, "Bob": 0, "John": 40]

Your expectation that you can mutate a dictionary passed into a function is wrong, for the simple reason that a dictionary in Swift is a value type. And the types inside the dictionary, String and Int, are values too. This means that, in effect, the parameter dict is a copy of the original dictAges. Nothing that you do to dict can have any effect on dictAges.

This is a sign that you should rethink your entire architecture. If it was wrong to enter a negative number as an age, you should have caught that up front, as it was being entered. In effect, your entire use of a dictionary of Ints as the model here is probably incorrect. You should have used a dictionary of Person, where Person is a struct with an age and a setter that prevents an age from being negative in the first place.

If you must have a function that runs through an arbitrary dictionary and fixes the values to be nonnegative, make that a feature of dictionaries themselves:

var dictAges : [String: Int] = ["John":40, "Michael":20, "Bob": -16]

extension Dictionary where Value : SignedInteger {
    mutating func fixAges() {
        for (k,v) in self {
            if v < 0 {
                self[k] = 0
            }
        }
    }
}

dictAges.fixAges()

In general, it's not good to mutate a sequence as you're looping through it, which is why you only get an immutable copy of the dictionary in your code.

Swift 4

I think a nicer approach could be to use map, and to take a more functional approach:

var dictAges : [String: Int] = ["John":40, "Michael":20, "Bob": -16]

func correctAges(dict:[String:Int]) -> [String:Int]
{
    let corrected = dict.map { (name, age) in age > 0 ? (name, age) : (name, 0) }
    return Dictionary(uniqueKeysWithValues: corrected)
}

dictAges = correctAges(dict: dictAges)
print(dictAges) // ["Michael": 20, "Bob": 0, "John": 40]

This way you can reuse this method for any [String:Int] dictionary.

Related