Specify a settable property/variable in a protocol

1.4k Views Asked by At

I would like my protocol to declare that there is a read/write property available. I have attempted it, but this does not work:

protocol EdibleThing {
  var eaten: Bool { get set }
}

class Pickle: EdibleThing { var eaten = false }
class RusticGrapefruit: EdibleThing { var eaten = false }

class Jar {
  let contents: [EdibleThing] = [Pickle(), RusticGrapefruit()]
  var nextItem: EdibleThing {
    return contents.last ?? Pickle() // Lazy pickle generation technology
  }
  func eat() {
    let food = nextItem
    food.eaten = true // (!) ERROR: Cannot assign to result of this expression
  }
}

What am I doing wrong? I think I've declared that the protocol has a get/set var called eaten, so why can't I set it?

3

There are 3 best solutions below

3
On BEST ANSWER

The protocol might be implemented by either classes and structs - that prevents you from changing the internal status of an instance of a class or struct implementing that protocol using an immutable variable.

To fix the problem you have to either declare the food variable as mutable:

func eat() {
    var food = nextItem
    food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}

or declare the EdibleThing protocol to be implementable by classes only:

protocol EdibleThing : class {
    var eaten: Bool { get set }
}  

Note that this happens because food is a variable of EdibleThing type - the compiler doesn't know if the actual instance is a value or reference type, so it raises an error. If you make it a variable of a class type, like this:

let food: Pickle = nextItem as! Pickle

the compiler knows without any ambiguity that it's a reference type, and in that case it allows the assignment. But I guess that breaks your app logic... so consider it just as an example

2
On

You're mutating food.

Replace let food = nextItem with var food = nextItem

1
On

The problem is that you can't mutate a property on a value type defined by let.

Even though both of RusticGrapefruit and Pickle are class implementations (reference types), the protocol could be assigned to a value type like a struct. The compiler detects a potential problem and stops us.

Two solutions:

  1. Change let to var (in my case, this would mean changing a lot of code that refers to objects of this type. Also, I like the semantic value and possible compiler optimizations from let)
  2. Declare the protocol as only valid for classes: protocol EdibleThing: class { }