-
-
Save algal/1ca25254e16504368c48 to your computer and use it in GitHub Desktop.
/* | |
This gist illustrates how Swift's "immutable" arrays are actually | |
mutable, and why that is sad. Load it into a playground to experiment. | |
"let"-defining an array should produce a truly immutable value. Instead, it | |
now produces a constant reference to a mutable, fixed-size array. This is like | |
C. So it is no better than C. And it's worse than Objective-C, where you | |
could produce a reliably constant array with code like: | |
NSArray * const ar = @[foo,bar,baz]; | |
I reported this as a bug with <rdar://problem/17181951>. | |
Fuller discussion at <https://devforums.apple.com/message/976600#976600>. | |
*/ | |
import Cocoa | |
// | |
// 1. non-constant arrays produce unnecessary aliasing problems: | |
// | |
// w is a constant array | |
let w:Int[] = [10,10] | |
// v is a constant array | |
let v = [w,w] | |
v // => [[10,10],[10,10]] | |
// but I am plainly allowed to mutate their contents! | |
v[0][0]=20 | |
// and now I've mistakenly mutated two slots in my 2d array, because of | |
// the aliasing problems associated with mutability: | |
v // => [[20,10],[20,10]] | |
// | |
// 2. non-constant arrays undermine the purpose of the inout parameter | |
// | |
// this func takes a non-inout parameter, so I would think it cannot mutate its arguments | |
func mutateFirstParam(arr:Int[]) -> () { | |
arr[0] = 20 | |
} | |
let Z:Int[] = [10,10] | |
Z | |
mutateFirstParam(Z) | |
Z // => surprise! Z has been changed. It was not a pure function. | |
// | |
// 3. non-constant arrays are inconsistent with Swift's constant dictionaries | |
// | |
// this dictionary is a variable, so I get to mutate it | |
var D = ["a":1] | |
D["a"]=2 | |
D | |
// this dictionary is a constant, so I do not | |
let d = ["a":1, "b":2, "c":3] | |
d | |
d["a"] = 5 // this line produces an error, as it should |
Using .unshare()
on any array reference causes a copy to be created. For example (executed in Swift REPL):
var s1 = [1, 2]
// s1 is equal to [1, 2] (s1 = [1, 2])
let s3 = s1
// s3 points to s1 (s3 = ->s1)
s1[0] = 123
// s1 is now equal to [123, 2] (s1 = [123, 2])
s3
// prints [123, 2], because what s3 points to changed
s3.unshare()
// copies what is referred by s3, which is s1, so s3 is now [123, 2] (s3 = [123, 2])
s1[0] = 1
// s1 is now equal to [1, 2] (s1 = [1, 2])
s1
// prints [1, 2], because s1 = [1, 2]
s3
// prints [123, 2], because s3 has not changed
The above is true for mutliple references as well:
var s1 = [1, 2]
// s1 is equal to [1, 2] (s1 = [1, 2])
let s4 = [s1, s1]
// s4 points to s1 (s4 = [->s1, ->s1])
s1[0] = 123
// s1 is now equal to [123, 2] (s1 = [123, 2])
s4
// prints [[123, 2],[123, 2]], because what s4 points to changed
s4.unshare()
// copies what is referred by s4, which is s1, so s4 is now [[123, 2],[123, 2]] (s4 = [[123, 2],[123, 2]])
s1[0] = 1
// s1 is now equal to [1, 2] (s1 = [1, 2])
s1
// prints [1, 2], because s1 = [1, 2]
s4
// prints [[123, 2],[123, 2]], because s4 has not changed
I'm surprised your code above works, where you call unshare() on s3, since the Swift Programming Language says:
“You ensure the uniqueness of an array reference by calling the unshare method on a variable of array type. (The unshare method cannot be called on a constant array.)”
Excerpt From: Inc, Apple. “The Swift Programming Language.” Apple Inc., 2014-05-27T07:00:00Z. iBooks.
This material may be protected by copyright.
Check out this book on the iBooks Store: https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=881256329
FWIW, i filed a radar at: radar://17229960 "Inconsistent immutability of arrays in Swift"
and also got this tweet stream going: https://twitter.com/dwarfland/status/476310789763395584
I also raised this issue on SO: http://stackoverflow.com/questions/24090741/how-do-you-create-an-immutable-array-in-swift
And there's an openrader: http://openradar.appspot.com/radar?id=6622822524780544