According to the Swift language guide Strings, Arrays, and Dictionaries are all copied on assignment to a variable. So wouldn't the author's primary assumption (that let a and var b refer to the same array) be false? And this whole issue is null?
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.
> According to the Swift language guide Strings, Arrays, and Dictionaries are all copied on assignment to a variable.
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).
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.
I wish I could upvote this a hundred times; rarely have I seen so well-laid out a post on such a subtle topic. Just as I would form a thought like, "the author needs to explicitly break this down as an issue of variables vs values", they did so.
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.
Regarding c++, const foo * is the same type as foo const * .
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.
rarely have I seen so well-laid out a post on such a subtle topic
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".
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).
The proposed solution is not just a matter of syntax but of types: a and b would not be compatible. So a would have type [Int] and b would have type [:Int:]. You simply couldn't pass b into arrayConsumer without explicitly noting the change:
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.
Yes, you could propagate the mutability to argument types (your syntax is the opposite of the one proposed by the OP, not that this matters to your argument), but that’s a lot of syntactic epicycles, all to deal with the top level of one collection type. Why is it so important to have this for Arrays, and not for Hashes, or for objects, for that matter?
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?
Are you kidding? Swift has a TON of revolutionary stuff over C, including algebraic data types, optionals, destructuring pattern matching, and a lot more. Rust too. There are a ton of new "C-like" languages that are light years beyond C99.
I meant C-like syntax. The laundry list of features you mentioned is present in lots of existing mature languages (not C).
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).
Just to expand on one language, Rust, it possesses semantics that most other languages don't have. The presented problem with Swift would be impossible. As soon as you make the mutable variable, ownership of the object is transferred, and it becomes a compiler error to use the original variable after that. This is backed by the type system, encompassing both mutability and lifetimes, so it isn't a runtime enforcing the lifetime rules.
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.
You complained about new C-like languages (C-like in syntax) not being any better than C. I mentioned some ways that new C-like languages (C-like in syntax) are much better than C. Now you're complaining that other languages already had those features. I don't contest that. Doesn't change the fact that your first comment was misguided.
You misunderstood my first comment. I meant new C-like-in-syntax have nothing revolutionary semantically across the board. This is a double blow as each new C-like syntax is difficult to master (compared to Lispy languages) and contributes nothing semantically new.
> You misunderstood my first comment. I meant new C-like-in-syntax have nothing revolutionary semantically across the board.
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.
Ah! That wasn't clear to me from your wording. I think I was especially confused because that means your argument seemingly boils down to "only the first language to have a feature is notable." (And I guess also that C-like languages are hard to learn?) But all languages stand on the soldiers of giants. I don't look down on Haskell simply because other languages had its features first.
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.
The silly thing is they already have NSArray (immutable) and NSMutableArray (mutable) on the Objective-C side. So you'd expect anybody working on putting this stuff together to be familiar with this. I guess the four possibilities in Objective-C are:
It may handle some of the subtly of assignment and references but there is a lack of understanding of Swift. If it is a general debate about the meaning of assignment it isn't just about arrays but structs and dictionaries too.
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'd probably prefer that var b = a would just generate an error. You would have to explicitly copy or clone an immutable object in order to create the variable.
I'm glad I'm not the only one thinking this. What's the point of Swift being "safer" if this isn't an error (or at least a warning). The standard library even includes -[mutableCopy], so it's a established convention.
agreed; this would be a nice exception and I'm sure in a few places the compiler could pick this up to add warnings.
Alternatively get it to throw an exception if the array is modified.
Much better than some [[]] notation which just makes me think of multidimensional arrays
This is certainly my favorite too. Implicitness and sugar are only worth their weight when their semantics are obviously illusory. That's not the case with this copy-on-assign business.
I don't understand what semantics are illusory? Swift semantics for value types (including structs, arrays dictionaries and strings) are copy on assignment (return and argument passing). The fact that most of the copies are optimised out doesn't actually change the semantics.
If that's always made clear then it's probably fine. I haven't played around in Swift enough to know it to be true.
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.
If I understand you correctly Swift does that. Only objects (defined as classes) are mutable reference types as far as semantics go.
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.
Yeah that makes perfect sense. The notion I'm getting at is where you say "declare a variable to contain". If you have only immutable objects then you can dispense with the idea of containment and use only equivalence. As an equivalent semantics you can have containment and copy-on-write everywhere. Under immutability they are the same.
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.
I'm not sure how the behaviour isn't uniform (unless you are getting on to classes).
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?
No a has the value [1,2,3] still in that scenario. Copy on assign semantics is for constants and mutable variables.
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]
I wouldn't recommend using === in production (except for objects of classes) either but for this demonstration it let me show the copy on write without going to assembly.
Glad we've reached a common understanding, I wasn't quite sure that I hadn't missed something.
I don't see anything wrong with the beta3 semantics. I see it as problematic that (=) is so casually overloaded between "assign" and "equivalate".
> 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 disagree with this post. The semantics are clear and simple: if you assign,maps to a function or return from a function any array you can treat it as a copy of the array. As a performance optimisation the copy usually won't happen as it won't be needed. If you are dealing with large enough arrays that it matters wrap the array in an object that can be passed by reference.
I strongly opposed to original semantics and blogged about it. I am happy with the replacement.
Then what is the point of having let vs var at all? I think the solution they came up with is the correct one given the design of swift and its use of let and var keywords.
I didn't appreciate that Rust makes that distinction, that really is quite elegant.
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).
The different types of mutability are also quite important for the concept of "borrowing" in Rust, which is simply taking a reference. The compiler ensures that ownership of the variable isn't transferred while any such reference is active, and only one mutable reference (of a mutable type) can exist at any one time. This means that bugs like that shown for Swift are impossible in Rust, because the language ensures no two things can ever modify it at the same time.
let/var solve a different problem than the question of whether the array itself is mutable. Look back at the OP, and in particular at the last example section, which lays out the behaviour of various combinations of let/var with mutable/immutable arrays. Each of these behaviours is desirable in different circumstances, and more importantly, by separating the concerns here it will make the language semantics easier to understand and apply to new examples.
I rather like the copy-on-write semantics of Beta3. And if the OP wants the Swift team to consider his/her comments, they should file a Radar like everyone else.
The syntax proposed has problems. Many languages allow you to declare nested data structures and a natural way to express an array of arrays in swift would be the following:
This is not a good solution. This adds complexity (syntactically and conceptually) without adding any feature. The immutability is already implied by the 'let' and 'var'.
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 is already implied by the 'let' and 'var'.
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:
A solvable ambiguity (nested C++ template parameters, anyone?) and besides, it was just an example. I don't think the author really meant to "propose new syntax", but rather say "this is a real problem that needs solving and I'm sure the Swift team can come up with syntax for it if they want it to be succinct".
We didn't change it. I assume the rewording was joeyespo's attempt to avoid misleading or linkbait content in the title. As the HN guidelines point out, that's fine.
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.