31

How do I create an immutable array in Swift?

A superficial reading of the docs would suggest you can just do

let myArray = [1,2,3]

But sadly this actually produces a mutable, fixed-size array. This mutability creates the usual puzzles with unsuspected aliasing and functions mutating their arguments:

let outterArray = [myArray, myArray]
outterArray[0][0] = 2000
outterArray //=> [[2000,2,3],[2000,2,3]]   surprise!

func notReallyPure(arr:Int[]) -> () { arr[0] = 3000 }
notReallyPure(myArray)
myArray // => [3000,2,3]

Not much better than C.

If I want immutability, is the best option really to wrap it in an NSArray like so:

let immutableArray = NSArray(myArray: [1,2,3])

That seems nuts. What am I missing here?

UPDATE (2015-07-26):

This question dates from the very early days of Swift. Swift has since then been updated so that immutable arrays are actually immutable, as answers below indicate.

algal
  • 27,584
  • 13
  • 78
  • 80
  • 1
    unfortunately, I think you are not missing something. IMHO this is a bad feature of Swift. – Michael Jun 06 '14 at 21:19
  • I'm pretty sure using a tuple will guarantee immutability, though I realize that loses the benefits of array-ness – Jiaaro Jun 06 '14 at 21:20
  • 2
    I hope much smarter people than I understand a good reason for this. This seems like madness. – algal Jun 06 '14 at 21:20
  • 2
    IMHO Swift is a very nice language but Strings, Arrays and Dictionaries are very badly designed. – Sulthan Jun 06 '14 at 21:20
  • Just curious, why you think it's nuts that you need to explicitly create the NSArray? Isn't the justification from Apple that the inferred type when creating an Array is that it is mutable, so you just need to be more explicit? – Tim Jun 06 '14 at 21:26
  • 1
    @Jeff, I think it's nuts because I think immutability should be the default. If Swift is going to go to the trouble of having its own collection types, instead of just using Cocoa, then we shouldn't need to use Cocoa just to get an immutable array. – algal Jun 06 '14 at 21:31
  • Fair point :) I'm now inclined to agree – Tim Jun 06 '14 at 21:43
  • Related: http://stackoverflow.com/q/24081009/11683 – GSerg Jun 28 '14 at 21:33

5 Answers5

27

This has changed with Xcode 6 beta 3. While arrays used to be semi-mutable, as you describe, with their elements changeable but their length fixed, now immutable arrays share the same value semantics as Dictionaries:

From the Xcode 6 beta 3 release notes:

• Array in Swift has been completely redesigned to have full value semantics like Dictionary and String have always had in Swift. This resolves various mutability problems – now a 'let' array is completely immutable, and a 'var' array is completely mutable – composes properly with Dictionary and String, and solves other deeper problems. Value semantics may be surprising if you are used to NSArray or C arrays: a copy of the array now produces a full and independent copy of all of the elements using an efficient lazy copy implementation. This is a major change for Array, and there are still some performance issues to be addressed. Please !see the Swift Programming Language for more information. (17192555)

The original information on arrays in the Swift book was updated on 7th July 2014 to reflect the beta 3 changes. (If you're using iBooks on a Mac, as I was, you may need to delete and re-download it to pick up the 7th July update—I couldn't get the thing to update automatically.)

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
  • 14
    I hope whoever wrote that blushed. "Immutability has a slightly different meaning for arrays" == "However, for arrays, immutability means mutability" – algal Jun 06 '14 at 21:25
  • I don't think you should be sharing Beta notes as its confidential and not public (you need an Apple Developer's account to access it). – Cthutu Jul 08 '14 at 11:31
  • 1
    @Cthutu You need a developer's account to access the beta version of Xcode itself, let alone the release notes, and people are discussing it everywhere. Bear in mind that [the NDA has been relaxed this year](http://oleb.net/blog/2014/06/apple-lifted-beta-nda/); there's not the same restrictions on discussion as in previous years. – Matt Gibson Jul 08 '14 at 12:15
8

Seems to be a bug and to be fixed soon.

Cited from Apple dev forum:

Question:

Inconsistency with let for Array and Dictionary

Final answer:

This is considered to be a bug, not a feature, and will be fixed in a later Beta.
-Chris

eonil
  • 83,476
  • 81
  • 317
  • 516
1

There is not a great answer for this, and it is bizarre.

You can, however, prevent accidental mutation of arrays as they flow through your program by calling yourArray.unshare(). This causes the array to be copied when it's assigned to a new variable.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 1
    Interesting. But you cannot call `unshare()` on a constant array only on a variable one! – algal Jun 06 '14 at 21:37
  • @algal: Yeah — like I said, it is bizarre. A constant array can't be shared, but can be modified. So if you want to prevent accidental mutation, you have to make the array mutable. Swift's mutability semantics are undoubtedly the grodiest thing about it. – Chuck Jun 06 '14 at 21:40
  • 1
    This is bananas. As far as I can tell, there's no way to stop a function from modifying the contents of an array (or to assert that it cannot do so). If the function modifies in place a "constant"-parameter-type array that is passed to it, calling `unshare()` before or after invoking the function has no effect. You have to trust functions to do the right thing. – Wes Campaigne Jun 14 '14 at 18:40
  • I am shocked about the fact that a constant array is actually a fixed length mutable array. I wonder if apple could fix this. This would probably make it not backwards compatible and change the semantics of existing code. So there is no hope anymore? Broken forever? – Lensflare Jun 16 '14 at 15:35
  • @Lensflare: Apple is evidently OK with making breaking changes at this point in the game (since Swift is not yet final). I am very skeptical about them doing anything about this, though, because this appears to be a deliberate choice (probably somehow related to an optimization for array speed). – Chuck Jun 16 '14 at 16:50
  • 1
    In Beta 3 now constant arrays are truly constant. – Lensflare Jul 11 '14 at 09:13
1

It is now possible.

From Apple Developer

If you assign an array or a dictionary to a constant, that array or dictionary is immutable, and its size and contents cannot be changed.

So now

let myArray = [1,2,3]

produces completely immutable array. Yay!

nicael
  • 18,550
  • 13
  • 57
  • 90
0

IMHO the simplest workaround is simply wrap it in the closure as follows:

let mutableElements =  [0,1,2,3]
let reallyImmutable = {[0,1,2,3]}
println(mutableElements)
for i in 0..mutableElements.count { mutableElements[i] *= -1 }
println(mutableElements)    // [0, -1, -2, -3]
println(reallyImmutable())
for i in 0..reallyImmutable().count { reallyImmutable()[i] *= -1 }
println(reallyImmutable())      // [0, 1, 2, 3]
println(reallyImmutable()[2])   // 2
let anotherImmutable = { reallyImmutable().map{ $0 * $0 } }
println(anotherImmutable())     // [0, 1, 4, 9]

You pay extra {} on declaration and () for each access but that also makes your code speak for itself.

Dan the Mutable Programmer

P.S. Wrote a wrapper class ImmutableArray.

dankogai
  • 1,627
  • 12
  • 7