Home > Enterprise >  Cannot assign to a property of an instance that get returned from a get-only subscript
Cannot assign to a property of an instance that get returned from a get-only subscript

Time:10-21

I return an instance from a get-only subscript, but I cannot assign to a property of the returned instance

struct Student {
    var id: String
    var attendance = 0
    var absence = 0
}

class StudentStore {
    var students = [Student]()
    subscript(id: String) -> Student? {
        students.first(where: { $0.id == id } )
    }
}

var studentStore = StudentStore()

var newStudent = Student(id: "2014901001")
studentStore.students.append(newStudent)

studentStore["2014901001"]?.attendance = 1 // error: Cannot assign to property: subscript is get-only

CodePudding user response:

The subscription doesn't have a setter. What you have is the implicit getter which is a short form of

subscript(id: String) -> Student? {
    get {
        students.first(where: { $0.id == id } )
    }
}

But it's impossible to update a value type item in an array with key subscription anyway.

An alternative is to add an update function to the store which takes the id, a keyPath and a value

func updateStudent<Value>(withID id: String, keyPath: WritableKeyPath<Student,Value>, value: Value) {
    guard let index = students.firstIndex(where: {$0.id == id}) else { return }
    students[index][keyPath: keyPath] = value
}

Getting the index is mandatory to be able to modify the item in the array directly.


Then you can replace

studentStore["2014901001"]?.attendance = 1

with

studentStore.updateStudent(withID: "2014901001", keyPath: \.attendance, value:1)

CodePudding user response:

Error show exactly what you have to change. Because subcript will return something likes:

let updateStudent = studentStore["studentStore"]

You can not change with let

So update with this:

if var updateStudent = studentStore["studentStore"] {
     updateStudent.attendance = 1
}

CodePudding user response:

When you do

studentStore["2014901001"]?.attendance = 1

You are trying to set one of the Student objects in the students array, aren't you?

However, the subscript getter doesn't return (a reference to) one of the objects in the students array. Due to the value semantics of the Student struct, you return a copy of the Student object that is in the students array. You can't achieve what you want if you just set the attendance of that copy. Therefore, the Swift compiler compiles your code to something like this:

let copy = studentStore["2014901001"]
copy?.attendance = 1
studentStore["2014901001"] = copy

You can see that it sets the setter with the modified copy again. But your subscript does not have a setter, which is where the error comes from.

To solve this, you can make Student a class, which has reference semantics. This enables you to return the Student object that is in the students array, allowing the caller of the subscript to set their attendance.

class Student {
    var id: String
    var attendance = 0
    var absence = 0
    
    init(id: String, attendance: Int = 0, absence: Int = 0) {
        self.id = id
        self.attendance = attendance
        self.absence = absence
    }
}

Alternatively, you can add a setter. However, that would allow nonsense like this:

studentStore["1"] = Student(id: "2")

You need to decide what you want to happen here.

  • Related