Mutating from a protocol preventing me from calling a method on class instance returns as computed property

I made a class (which I shall call Vector in this post) conform to RangeReplaceableCollection.
Note: Vector is a class, it is not a struct.

I have a container class which returns a Vector as a computed property (just get, not set):

class Container {
    ...
    public var v : Vector {  <fetches and returns a Vector instance from some general store> }
    ...
}

Given the above, this is legal:

     let container = <make me a Container>
     var v = container.v

     // this is legal
     v.append(element) 

but this is not (and I feel it ought to be):

    // Doesn't compile: error is:
    // Cannot use mutating member on immutable value: 'v' is a get-only property
    container.v.append(element)  

I understand why I'd get the error if Vector was a struct, not a class. But it's a class, so this should be legal (and it's useful too). In this case, the public variable isn't really a member of the container, it's stored behind the scenes, so mutating it makes sense. Also, given that I can work around the bug by writing the legal code, now it's a restriction that's annoying me.

Q1: Should my second syntax be legal, or did I miss something?
Q2: Anybody got a trick that'll let it be legal?

Might be the same issue as here?

1 Like

i don't know v.append(element)..
it means v have a function named append?, maybe you vector is a struct not a class.
can you give me more details to let me help you

You missed something, but everybody does because Swift is very special on the meaning of "mutating".

In Swift "mutating" means that the whole struct/class can be replaced. Mutating methods are allowed to return a different instance:

protocol P {
    init()
    mutating func mutateMe()
}
extension P {
    mutating func mutateMe() { self = Self.init() }
}
class Foo: P {
    required init() { }
}

var foo = Foo()
print(ObjectIdentifier(foo)) // 0x00007f831cf05370
foo.mutateMe()
print(ObjectIdentifier(foo)) // 0x00007f831caea490

This is the general context the language has to take in account.

Now classes generally reuse their instances in their mutating methods :-) This gives the conflict you are experiencing. You are not the first, believe me. I'm not here to say if this a design flaw or a boon. But this is something to witness and learn, for sure.

I'm curious to know an answer too.

I think the trick is to provide your own implementation of append(_ newElement: Element) in the class implementation of Vector.

For me this compiles:

final class Vector: RangeReplaceableCollection, CustomStringConvertible {
	private var items: [Int] = []
	
	subscript(_ index: Int) -> Int {
		get {
			return self.items[index]
		}
		set {
			self.items[index] = newValue
		}
	}
	
	var startIndex: Int {
		return self.items.startIndex
	}
	
	var endIndex: Int {
		return self.items.endIndex
	}
	
	func index(after i: Int) -> Int {
		return self.items.index(after: i)
	}
	
	func append(_ newElement: Int) {
		self.items.append(newElement)
	}
	
	var description: String { return "\(self.items)"}
}


class Container {
	private var store : [Int: Vector] = [ 0: Vector() ]
	
	public var v : Vector {  return self.store[0]! }
}

let container = Container()

var v = container.v

v.append(4)

print(container.v)
// [4]

container.v.append(7)

print(container.v)
// [4, 7]

Interestingly, if I remove the append(_ element: Int) and the container.v.append(7) line, this code terminates with a Terminated due to signal: SEGMENTATION FAULT (11)

There are two problems with that suggestion.

  1. There are quite a lot of mutating methods that one gets as default implementations when you make a range replaceable collection. It is somewhat tedious (and not future proof) to supply implementations of all of them.

  2. The preferred implementation would be to call back to the default inherited method. But since this is mutating, I would require my own implementation to call the mutating protocol defined default, which would further require that my own method be marked mutating.

Yuck.

This is actually getting worse the longer I study the problem, not better.