AlbertUI
AlbertUI

Reputation: 1517

Create mutating set for Index in Collection subscript in Swift

I'm trying to modify this extension in order to get mutability:

extension Collection where Element: Equatable {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
            indices.contains(index) ? self[index] : nil
    }
}

I want to achieve this working:

var array = ["Hello", "World"]
array[safe: 5] = "Other word" // This will not be setted because index is out of bounds, but error won't be throwed.

When I try to modify the subscript extension...

extension Collection where Element: Equatable {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index) {
                self[index] = newValue        <<<-- ERROR
            }
        }
    }
}

... I get this error: Missing argument label 'safe:' in subscript

Upvotes: 3

Views: 869

Answers (2)

Sweeper
Sweeper

Reputation: 272760

Collection's subscript is get-only.

MutableCollection is the protocol that Array conforms to, that declares the settable subscript that we all know and love. After all, your extension requires the collection to be mutable, and not all Collections are mutable, only MutableCollections are.

extension MutableCollection {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self[index] = value
            }
        }
    }
}

Note that I also added a check in the setter for the case when newValue == nil (because the subscript has type Element?, you can assign nil to it!). In that case, the subscript won't do anything. The Element : Equatable constraint is also not needed.


Technically, this can also be done on RangeReplaceableCollections, which is another one of those protocols that Array conforms to. But subscripts are supposed to be O(1) time, and replaceSubrange isn't necessarily so.

extension RangeReplaceableCollection {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self.replaceSubrange(index...index), with: CollectionOfOne(value))
            }
        }
    }
}

Upvotes: 6

David Pasztor
David Pasztor

Reputation: 54745

The problem is that Collection has no mutable subscript setter. To get that, you need to extend MutableCollection instead.

extension MutableCollection where Element: Equatable {
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript(safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self[index] = value
            }
        }
    }
}

Upvotes: 1

Related Questions