On Swift’s array semantics
yinwang0.wordpress.com
yinwang0.wordpress.com
https://developer.apple.com/library/prerelease/mac/documenta...
Assignment and Copy Behavior for Strings, Arrays, and Dictionaries
Swift’s String, Array, and Dictionary types are implemented as structures. This means that strings, arrays, and dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
This behavior is different from NSString, NSArray, and NSDictionary in Foundation, which are implemented as classes, not structures. NSString, NSArray, and NSDictionary instances are always assigned and passed around as a reference to an existing instance, rather than as a copy.
NOTE The description above refers to the “copying” of strings, arrays, and dictionaries. The behavior you see in your code will always be as if a copy took place. However, Swift only performs an actual copy behind the scenes when it is absolutely necessary to do so. Swift manages all value copying to ensure optimal performance, and you should not avoid assignment to try to preempt this optimization.
This might've been a recent change in docs. Quoting part of the same section from my copy of the Swift book downloaded a while ago (emphasis mine):
‘The assignment and copy behavior for Swift’s Array type is more complex than for its Dictionary type…
‘If you assign an Array instance to a constant or variable, or pass an Array instance as an argument to a function or method call, the contents of the array are not copied at the point that the assignment or call takes place. Instead, both arrays share the same sequence of element values. When you modify an element value through one array, the result is observable through the other.
‘For arrays, copying only takes place when you perform an action that has the potential to modify the length of the array.’
As an aside, it would be nice if Apple offered some kind of change log or a “what's changed” page for Swift docs (though to my knowledge they had made no promises regarding spec stability yet).
https://developer.apple.com/library/prerelease/ios/documenta...
Note that you have to delete the book in iBooks and download it again to get the new version.
Beta 2 behaviour matched the docs at that time but there was plenty of pushback about the behaviour in radars, Apple dev forums[0] and on the wider web[1].
Chris Lattner announced[0] the planned change on the forum 19th June.
[0] https://devforums.apple.com/message/989944#989944
[1] http://blog.human-friendly.com/swift-arrays-too-swift-and-fl...
Part of the difficulty here (and part of why others are having a hard time understanding it) is that immutable arrays and mutable arrays are really two different types, and unlike many other types (which are inherently one or the other), you really want both. A Java analogy is that there are two different string types: String, which is immutable, and StringBuilder, which is mutable[0]; but this is a property of the value, not of the variable. (Other languages have similar string classes, and Swift might well also.) For C++ folks, the distinction between mutability of variable and mutability of value is shown in the placement of "const", i.e. "const foo ∗" vs "foo const ∗" (which of course leads us to "const foo const ∗", and the whole thing is a syntactic mess, but anyway), and object values which are mutable can behave differently from values which aren't even when the underlying type is the same. In your own code in any language, of course, you can design a class so that its objects are immutable simply by not giving the user any access to set instance variables; and if you wanted both mutable and immutable versions, even without particular language support, you could make a MutableFoo and an ImmutableFoo that are copy-constructible from each other.
What may confuse the issue here is the idea that we might want brief syntax for both kinds of array. But whatever the language designers decide, the OP is exactly right to point out that they really, really need to think this through and divorce mutability of values from mutability of variables.
[0] Technically three, because StringBuffer is also there, but that's not relevant to this discussion.
For consistency, one should postfix the const to the thing you want to be immutable, so foo const * is a pointer to an immutable foo, foo * const is an immutable pointer to a mutable foo, and foo const * const is an immutable pointer to an immutable foo. It works pretty well, as far as I can see.
I'll have to disagree. I thought the author was quite muddled in their thinking on values vs variables. One of the biggest misunderstandings in the article, and the reason why the proposed "solution" is worthless, is that all the examples discussed are of the form:
let a = …
var b = a
while in reality a much more common "assignment" situation is that an array gets passed as a procedure argument, which has consistent behavior in both the older and the newer Swift array semantics, but would turn into a mess with the proposed "fix".Can you explain why?
var a = [1,2,3]
var b = [: 2,3,4 :]
func arrayConsumer(arg: [Int]) {}
Under the proposed semantics, arrayConsumer(a) would make arg an immutable variable referencing a mutable array (So assigning to arg[x] would be permitted), while arrayConsumer(b) would make arg an immutable variable referencing an immutable array (So assigning to arg[x] would be forbidden). arrayConsumer(b.immutableCopy())
Similarly, if you had a function that expected a mutable array, you would not be able to pass a in without making a mutable copy of it. func mutableArrayConsumer(arg : [:Int:]) { ... }
var mutableA = a.copyMutable()
mutableArrayConsumer(mutableA)
(You want to keep a reference to the mutable copy since a function that explicitly takes in a mutable array probably does something relevant to it.)This solves the problem neatly and statically without any implicit copying or conversions to trip you up.
The Swift view, as I understand it, is that there are some types ( “struct” instances and scalars) passed with value semantics, and some types (“class” instances) passed with reference semantics, and Arrays were reclassified from being the latter to being the former. This seems to me a sensible way of targeting the common case (passing arrays in Cocoa is quite common, but passing mutable arrays is extremely rare) without burdening the language with a full set of C++ style const designations.
A meta comment (not on Swift): the number of new languages with C-like syntax and nothing revolutionary semantically is baffling. Do we really need to master yet another C-like syntax to do X where a library (or an existing language) would have sufficed? Is this the new way to create technological walls?
Edit: Not to mention automated memory management!
I don't understand the need to create something rickety without significant innovation (over any relevant language (e.g. SML, Haskell, Racket) and not just C).
The more I learn about Rust, the more I realise how similar it is to Haskell. Even the error handling and IO uses monad-like constructions.
Are you saying that the new features in Swift/Rust/C#/etc. aren't revolutionary? Or that they're not "across the board"? Or that they're not semantic? Which part do you disagree with?
> This is a double blow as each new C-like syntax ... contributes nothing semantically new.
This is demonstrably false. Do you mean that you don't think the new semantics are revolutionary enough? Or "across the board" enough?
Either way I have to disagree with you. Automated memory management is a semantic game changer, and applies nearly everywhere. Same goes for type inference, and optionals. ADTs are huge too. If your argument is that these are not important semantic improvements over C, then IMO you could not be more wrong.
E.g,
> Automated memory management is a semantic game changer
That has been there in other languages for decades.
By "across the board," I meant across all languages not just C-like languages.
Python, for example, has a list type (mutable), and a tuple type (immutable). Swift could simply adopt the same thing and use `(1, 2, 3)` for immutable arrays and `[1, 2, 3]` for mutable arrays. It would break backwards compatibility but it is a beta.
Every other language (Ruby, Python, PHP, Javascript, for example) that uses `[]` as syntax for arrays always uses it as a mutable array type. I don't see why Swift shouldn't do the same, and provide a separate type and possibly new syntax for the immutable version.
NSArray *const p;
NSMutableArray *const p;
NSArray *p;
NSMutableArray *p;
("const NSArray *p" and so on makes no sense, as I understand it - the Objective side of Objective-C being not big on const.)If you want to opt in to a shareable mutable array it is easy to implement it by wrapping it in an object (class based) and passing that around.
I'm used to the memory-free semantics where equality equivalates a mathematical variable with a value. Assignment doesn't have a place here and it works very well with immutable values. Only mutability needs the notion of assignment so that you have boxes to subject to mutation.
Declaring constants with let has the effect you are talking about for equality. If you want to you can stick to that.
Where you declare a variable to contain a struct, dictionary or array with var you can mutate it but it only affects your copy so it is equivalent to creating a new instance with the updated vale and assigning it back to the same variable.
With the copy on write behaviour if there are no other variables or constants pointing to the original instance it can skip the copy for efficiency without affecting the semantics.
The trouble is that if you try to have both "name-equivalence" and "containment" and mutable things. At this point, copy-on-assign almost works uniformly. Except unless something like
var a = x
var b = a
does not refer to three separate containers, only at most two if x were immutable. At that point I feel like the explicit copy-on-assign might be better.Taking your example assuming x has been declared let x = [1,2,3] then the code you show will behave exactly as if the array had been copied but underneath it would actually be sharing a single bit of memory and have skipped/postponed the copies.
If the next line was b[0] = 5 then a copy would be made so x and a would still be [1,2,3] and b would be a separate array [5,2,3].
The behaviour will be exactly the same as copy on assign. You might be able to identify that it hasn't copied with the identity operator === but why would you need to know?
var a = [1,2,3]
var b = a
b[0] = 10]
> a
[10,2,3]
and so copy-on-assign only applies when assigning an immutable reference to a mutable one.Just tested in the playground:
var a = [1,2,3]
var b = a
a == b // true
a === b // true
b[0] = 10
a // [1,2,3]
b // [10,2,3]
b[0] = 1
a == b // true
a === b // false
a // [1,2,3]
b // [1,2,3]Observing sharing using (===) is still dangerous, but presumably that can be saved by wrapping it in sufficient warning.
Glad we've reached a common understanding, I wasn't quite sure that I hadn't missed something.
> Now aliasing an array will cause the array to be copied. Isn’t that weird?
But assignment to a mutable reference is just that, assignment not equivalation. It is not merely aliasing. We do not desire the ability to freely replace "b" with "a" so why assume it holds?
Sure this might vary from programmer's mental models, but that is the fault of the programmer after a point. I expect all hammers to work roughly the same, yes, but I expect a laser cutter to work under very different principles. I must learn them.
I strongly opposed to original semantics and blogged about it. I am happy with the replacement.
Rust makes that exact difference[-1]: `mut` can be used either at the binding level
let mut a: ∫
or at the type level: let b: &mut int = &mut 1;
Both are references (~pointers) to an int, but `a` is a mutable reference, it can be changed to point to a new value[0]: a = &2;
because b is an immutable reference it can't point to a new value and b = &mut 2;
is a compilation error: error: re-assignment of immutable variable `b`
however b's value can be modified in place: *b += 1;
whereas trying that with a: *a += 1;
is an error: test.rs:5:5: 5:7 error: cannot assign to immutable dereference of `&`-pointer `*a`
`a` is a mutable reference to an immutable value, `b` is an immutable reference to a mutable value.[-1] and so does C though it has the opposite defaults, you can have an
int const *
— a mutable pointer to an immutable int (you can reassign the pointer but not change the pointee) — and an int * const
— an immutable pointer to a mutable int (you can't reassign the pointer but you can alter the pointee)[0] the code does not work as-is because lifetimes, but close enough
By way of comparison (to Rust and the original article), here is Scala:
scala> val a = scala.collection.immutable.Set("apple", "orange")
a: scala.collection.immutable.Set[String] = Set(apple, orange)
scala> a = Set()
<console>:8: error: reassignment to val
a = Set()
^
scala> val b = scala.collection.mutable.Set("cherry", "plum")
b: scala.collection.mutable.Set[String] = Set(cherry, plum)
scala> var c = b
c: scala.collection.mutable.Set[String] = Set(cherry, plum)
scala> b += "pear"
res6: b.type = Set(pear, cherry, plum)
scala> c
res7: scala.collection.mutable.Set[String] = Set(pear, cherry, plum)
scala> c = a
<console>:10: error: type mismatch;
found : scala.collection.immutable.Set[String]
required: scala.collection.mutable.Set[String]
c = a
^
In other words: var versus val has to do with mutable bindings; mutable bindings allow you to mutate the value but not the type; object mutability within the bindings is governed by the object's own type (as the original author recommended for Swift). let a = [[1,2,3],[6,7,8]] let a = [: [: 1, 2, 3 :], [: 6, 7, 8 :] :]
or let a = [ [: 1, 2, 3 :], [: 6, 7, 8 :] ]
etc., depending on what mutability you want.Additionally, specifying that the RValue "[1,2]" has mutability is meaningless. The expression "[1,2]" is represented as a constant until it is stored in a variable (it should be represented in the MACH-O constant data section). It is the variable which has the concept of mutability.
The proposal is similar to proposing the following declaration in C:
"int i = const 1;"
Which is meaningless, because the '1' can never be not-const
The immutability of the binding and that of the value are different concerns. let and var are about the binding's mutability, trying to munge them together only ends up in tears.
> The proposal is similar to proposing the following declaration in C:
No. The proposal is similar to the following C:
int const * i;Really? I don't remember that, and i was writing Java a long time ago. I'd be interested to be reminded about this bug.