Defining both a mutable collection and an immutable collection by code reuse with struct?

(Davidbaraff) #1

I had written this:

   struct Vector<SOType: SerializableObject> : RangeReplaceableCollection {
      ...  // all the code you would expect to do the conformance

when I realized that in some cases, I wanted to hand back a collection which needed to be treated as read-only. Since there's quite a bit of code to write in making a type conform to RangeReplaceableCollection, I thought, no problem, I'll use inheritance on structures and first write

  struct ImmutableVector<SOType: SerializableObject> : Collection {
     // implements everything you need for a collection

  struct Vector<SOType: SerializableObject> : ImmutableVector<SOType>, RangeReplaceableCollection {
    // adds in just those few extra things you need to be able to modify the collection

I figured that inheritance would stop me from having to duplicate a lot of code. Then, of course, I realized you can't inherit structs.

So what's the sensible way to implement both a mutable and an immutable version of a collection with lots of specialized behavior with as little duplication as possible? I.e. both the mutable and immutable version require an Iterator class, etc. I don't want to replicate that. I don't want to have to write all my "startIndex" and "endIndex" etc. definitions. I feel like I should be able to just add a few more lines of code to piggy-back the mutable collection off the immutable one, but I'm just missing how to get it.


(Jordan Rose) #2

Since structs are value types, you shouldn't need mutable and immutable variants. When you mutate one, it creates a new value, always.

If your struct is backed by referencey storage, you'll need to do a "copy on write" to avoid mutating storage that's backing another value of the same struct. Swift has an extra feature to make copy on write more efficient: isKnownUniquelyReferenced. Here's a quick blog post about it (under an older name, isUniquelyReferencedNonObjC):

(Davidbaraff) #3

Sorry, you misunderstand. They may be structs, but when you copy them, they’re still pointing to the same underlying storage.

Think of the struct as a really lightweight proxy, so in essence, this has class semantics. (And I would have declared it as a “class” but then the protocol gives you mutating class functions, which is unpleasant.)

The issue is that I want to hand back an API to a person which doesn’t allow change at all through this vector. Essentially, I’m giving the user a read-only view of the underlying collection.

There’s a separate, more fine-grained and heavily error-laden API when you want to mutate it.

mutable collections in swift don’t let you throw from the subscript accessor, so i don’t want to let the user mutate the collection, (where an error can occur for any operation) using the mutable collection API.

Does that make sense?

(Jordan Rose) #4

So, you want this to have reference semantics (when mutable). Okay.

In that case, you can possibly get some abstraction with a protocol that both conform to, but yeah, Swift doesn't have struct inheritance, mainly to avoid the "slicing" problem that's well-known in C++. (In theory a language could support some kind of "inheritance without subtyping", but at some point the added complexity doesn't pay for itself.)

At some point we may get something like [Proposal Draft] automatic protocol forwarding, which (I think) would help a lot for cases like this.