Understanding computed properties

How come that if I have a struct:

struct Book {
      var pages = 10
 }

var books = [Book()]

I cannot mutate it by a computed property:

var bookWithTenPages {
     books.first { $0.pages == 10}!
}

bookWithTenPages.pages = 5 // will not work!

you can, if you declare a computed property setter:

var bookWithTenPages:Book 
{
    get 
    {
         books.first { $0.pages == 10}!
    }
    set(value) { ... }
}
2 Likes

Thanks @taylorswift !
I find it interesting that the value in the setter is a book, since Iā€™m only changing a single property on the book, the pages property.
I guess this is so because structs are immutable?

Yep, if it was a class you could mutate it. But for data like Book it's best to use a struct

The mutation of a struct value "in place" actually behaves like you've read it, changed the copy, and assigned the copy back.

So your snippet:

bookWithTenPages.pages = 5

Behaves pretty much like:

var localCopy = bookWithTenPages
localCopy.pages = 5
bookWithTenPages = localCopy

This makes sense. Structs are a value type, whose identity is defined by their value. Mutating a struct gives it a new identity, so it makes sense to have that hit your setters, didSet observers, etc.

Constrast this with classes, whose identity is based off their in-memory address.
mutating those objects in-place doesn't change their memory address (only the values at that address), so their identity doesn't change.

1 Like
var bookWithTenPages: Book {
    get {
        books.first { $0.pages == 10}!
    }
    set {
        print("\(#function): \(newValue)")
        bookWithTenPages = newValue // Attempting to modify 'bookWithTenPages' within its own setter, Function call causes an infinite recursion
        // šŸ˜¤šŸ˜©šŸ„ŗ?? what to put here to make the change "stick"?
    }
}

you would need to find the index of the first ten-page book in books, and assign to that, since bookWithTenPages is just an accessor.

alternatively, you can compute the index, and yield a reference to self.books[index] inside a _modify.

var bookWithTenPages: Book 
{
    ...
    _modify 
    {
        let index:Books.Index = self.books.firstIndex { $0.pages == 10 }!
        yield &self.books[index]
    }
}
3 Likes