65.9K
CodeProject is changing. Read more.
Home

When a Swift Immutable Collection Isn't Or Is At Least Immutable-ish

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

Feb 25, 2015

CPOL

2 min read

viewsIcon

4614

When a Swift immutable collection isn't or is at least immutable-ish

Take the following code: struct Foo { var value: Int } let foo = [Foo(value: 1), Foo(value: 2)].

Take the following code:

 struct Foo  
 {  
     var value: Int  
 }  
   
 let foo = [Foo(value: 1), Foo(value: 2)]  

By using 'let foo', an immutable array has been created. As such, no members of this array can be replaced nor can an element's contents be modified. As such, both statements below result in compilation errors.

 foo[0] = Foo(value: 777) // error!  
 foo[0].value = 8 // error!  

If foo were declared var, then both of the above statements would work.

This changes slightly when using reference types:

 class Bar  
 {  
     var value: Int = 0  
       
     init(value: Int)  
     {  
         self.value = value  
     }  
 }  
   
 let bar = [Bar(value: 1), Bar(value: 2)]  
   
 bar[0] = Foo(value: 777) // error!  
 bar[0].value = 8 // allowed  

The first case of trying to replace an element fails as before but modifying the instance referenced by that element is permitted. This is because the immutable collection holds a reference to the instance of Bar. Modifying the instance does not change the reference so the collection remains unchanged.

If you're making an effort to make your code as immutable (const) as possible (I'm from a C++ background so I am endeavour to making everything as const I can), then this is a gotcha to lookout for.

The only way to make Bar properly const is to provide a private setter for the member, assuming it's defined in its own source file (so this example doesn't work in a Plaground), i.e.,

 class Baz  
 {  
     private(set) var value: Int = 0  
       
     init(value: Int)  
     {  
         self.value = value  
     }  
       
     func f()  
     {  
         value = 88;  
     }  
 }  

Which now prevents value being assigned too.

 let baz = [Baz(value: 1), Baz(value: 2)]  
 baz[0].value = 8 // error!  

Even if all the properties have private setters, there might be methods on the reference type that modify the instance, such as f() above. Just having an immutable collection of reference types is not sufficient to stop these mutating the instances referred to. Invoking f() in the above collection will change the value to 88, i.e.

 println("\(baz[0].value)") // Gives 1  
 baz[0].f()  
 println("\(baz[0].value)") // Gives 88  

This differs to C++ where if a collection is const or is returned as a const reference (from a method) than not only is the container immutable but so are its contents. It would be good if Swift were to gain a mechanism that would mark the collection and its contents completely immutable other than having to use a value type.

Anyway, beware of making a collection of reference types immutable and assuming that both the collection and its contents cannot be modified!