Do not use this subscript to modify dictionary values if the dictionary’s Value type is a class. In that case, the default value and key are not written back to the dictionary after an operation.
Unfortunately, the nature of this behavior is not the class itself but lack of modification thus write back to the dictionary with set:
Therefore this code doesn't work as one may expect:
var dict = [Int: String]()
let value = dict[1, default: "test"]
print(value) // prints `test`
print(dict) // prints `[:]` because no mutating operation occurred
I wonder if that could be documented at the same page?
Other things that I was trying to use it as some analogue to c++ std::map::emplace(...) method that would add if element does not exist.
// avoid double lookup and hash calculation, create big object once:
_ = dict.addIfNotExists(key, /* autoclosure */ .init(...))
My main scenario is to create some big object just once.
So far I write the following way or wrap it to the function as above:
if dict[key] == nil {
dict[key] = info
}
But I wonder if there is a better way doing this...
This should have had a mutating get to begin with. It's the only reason to ever bother with this subscript. If you don't want a mutating get, then just use ??. I'm surprised it got through review as-is, but it was a long time ago.
extension Dictionary {
@inlinable subscript(
key: Key,
valueAddedIfNil value: @autoclosure() -> Value
) -> Value {
mutating get {
self[key]
?? { [value = value()] in
self[key] = value
return value
} ()
}
}
set { self[key] = newValue }
}
let test = "test"
var dict = [Int: String]()
#expect(dict[1, valueAddedIfNil: test] == test)
#expect(dict == [1: test])
dict[2, valueAddedIfNil: test].append(test)
#expect(dict[2] == "\(test)\(test)")
No, unfortunately (or fortunately?), it doesn't work. That was actually the thing that really confused me. I knew that adding classes (which present in official documentation). But it also didn't work but was never digging into details.
But turned, that adding value without modification never works for value types due to modify never called, i.e.:
let value = dict[1, default: "test"].count // or whatever non-mutating method is used
print(value) // prints `4`
print(dict) // prints `[:]`
I would say it is not intuitive and rather error prone. But now I think that maybe I should've put that in documentation section to add this note to the official docc here (same as for class): subscript(_:default:) | Apple Developer Documentation
Yeah, I would also not be much concerned about integers or other basic primitives.
And I see a solution from @lukasa to avoid hash re-calculation in other thread:
So, if I ever have the problem in hashing at this path I know what to do, so can put it to some helpers.
But I would vote to have this function in stdlib!
Seems pretty sensible to add a null-coalescing assignment operator ??= as suggested over in the other thread, which C# and JavaScript already have for example: dict[key] ??= info.