Cannot assign to property: 'self' is immutable?

I'm confused by this error. Is there a way around it? The ObservedObject here is ObservableObject which extends AnyObject. So I don't see why setting one of its properties should cause struct mutation.

Cannot assign to property: 'self' is immutable
Mark method 'mutating' to make 'self' mutable

The suggestion does not help, since that transfers the problem to var body, which can't be marked as mutating.

import SwiftUI

protocol ExerciseWithNoteRange {
    associatedtype Restriction: Identifiable & Equatable
    var noteRange: ClosedRange<Int> { get set }
    var restriction: Restriction { get set }
}

struct ExampleGenericView<EX: ExerciseWithNoteRange & ObservableObject>: View {

    @ObservedObject var exercise: EX
    let restrictionsInOrder: [EX.Restriction]
    let toString: (EX.Restriction) -> String
    
    var body: some View {
        List(restrictionsInOrder) { r in
           listItem(r)
        }
    }

    private func listItem(_ r: EX.Restriction) -> some View {
        Text(verbatim: toString(r))
        .onTapGesture {
            exercise.restriction = r  // ERROR HERE
        }
    }
}

1 Like

Well, it works if I add AnyObject to my protocol. That seems like a bug, since it's already part of ObservableObject.

protocol ExerciseWithNoteRange: AnyObject { ...

Hello,

This forum is only about the Swift language. Apple has requested that questions/comments about their proprietary frameworks (such as SwiftUI) should be asked on the Apple developer forums .

Thank you.

(And sorry - I don't like to do this)

Okay. I've tried to not ask questions here like "how do I layout a button", etc. But this one seems like it might be about the language, and SwiftUI is just an example that brings up the situation with structs, generics, and mutation. I don't know.

Sadly, in my experience the Apple developer forums are mostly a place to hear your voice echo into the void.

7 Likes

Understanding how structs and mutation applies in regular Swift does not automatically mean an understanding of how they apply in SwiftUI. SwiftUI has its own ideas about these concepts, and yes, it can be difficult to follow.

The example you give makes heavy use of SwiftUI-specific types, making it difficult to determine if this is a language question or not. If you can reduce it to a non-SwiftUI example, that would help.

I understand that many are unhappy with the Apple developer forums. You can also try StackOverflow or another site - but this site in particular has been asked not to provide support for SDK frameworks, and to instead direct developers to the ADF.

This question is actually on topic. Minimal example that shows the same issue with no SwiftUI specifics:

protocol OtherProto: AnyObject {
}

protocol MyProto { // add ": AnyObject" to fix
	associatedtype Restriction: Identifiable & Equatable
	var restriction: Restriction { get set }
}

struct ExampleGenericView<T: MyProto & OtherProto> {
	var exercise: T

	func foo(_ r: T.Restriction) {
		exercise.restriction = r  // ERROR HERE
	}
}
8 Likes

Ah, you beat me to it! I was just about to post that in response to Karl's suggestion. And we can make it a bit simpler still:

protocol Proto1 {
    var someString: String { get set }
}
protocol ObjectProto: AnyObject {}

struct ExampleGenericStruct<T: ObjectProto & Proto1> {

    var object: T
    
    func foo(s: String) {
        object.someString = s  // Error here: Cannot assign to property: 'self' is immutable
    }
}

So the question is: should the compiler be able to deduce that T is AnyObject, without having to add it to the other protocol?

1 Like

Good point! In the future I will try to reduce my example code. It definitely helped here.

1 Like

I see. That certainly does help.

It's actually possible to reduce the issue even further:

protocol MyProto { // add ": AnyObject" to fix
  var restriction: Int { get set }
}

extension MyProto where Self: AnyObject {
  func setRestriction(_ newValue: Int) {
    self.restriction = newValue   // Error: Cannot assign to property: 'self' is immutable
  }
}

If the AnyObject constraint is part of the protocol declaration, the assignment in the extension is allowed. But if the constraint is only on the extension, we get an error.

Certainly seems like it's worth filing a bug.

2 Likes

This is actually intended behavior, consider adding the following code:

protocol MyProto2: MyProto {
  init()
}
extension MyProto2 {
  var restriction: Int { 
    get { 0 }
    set { self = init() }
  }
}

final class Foo: MyProto2 {
  init() {}
}

In this case setting restriction is actually mutating, even though it is a class.
the reason adding : AnyObject fixes things is it changes var restriction: Int { get set } from meaning var restriction: Int { get mutating set } to var restriction: Int { get nonmutating set }

6 Likes

Wow, that is confusing. But I learned something new about Swift today. We can't set self in a normal property setter on a class (or in instance methods?), but we can define such a setter in a protocol extension and then "inherit" it in a class.

So basically code, like this:

obj.x = 1

... even when you know obj is an AnyObject, might reallocate a new object and change the identity of what obj points to.

I'm trying to imagine where that is useful. I don't think I've ever used it.

2 Likes

You’re correct, and thanks for persisting. The moderators have said that questions that happen to use SwiftUI types but come down to how the language works are on topic and absolutely welcome here. Some questions are pretty clearly not that (e.g., how to lay out a button) but when it’s not so clear I wouldn’t refrain from asking a question in these forums for fear it’s off-topic. As demonstrated here, it can lead to helpful learning.

9 Likes

The compiler suggest "mark func setRestriction(_:) mutating":

protocol MyProto/*: AnyObject*/ { // add ": AnyObject" to fix
    var restriction: Int { get set }
}

extension MyProto/* where Self: AnyObject*/ {
    mutating func setRestriction(_ newValue: Int) {
        self.restriction = newValue   // Error: Cannot assign to property: 'self' is immutable
    }
}

why not just do this to solve the problem?

Making protocol members mutating makes it a bit more inconvenient to work with classes (which don't directly support mutating members) and as a corollary you can have some surprising behavior:

protocol P {
    init()
}

extension P {
    mutating func f() {
        self = .init()
    }
}

final class C: P {}

let c1 = C()
c1.f() // error: cannot use mutating member, 'c1' is a 'let' constant

let c2_let = C()
var c2_var = c2_let
c2_var.f()

c2_let === c2_var // false, the instance has changed!
3 Likes

Because in the original question/situation, I need to implement SwiftUI's View.body, which is not mutating.

1 Like

4 posts were merged into an existing topic: Members answering questions about Apple frameworks

A post was merged into an existing topic: Members answering questions about Apple frameworks

This is exactly right; thank you, Xiaodi.

(Please, no more posts about whether this is on-topic in this thread. It's on-topic.)

2 Likes