7 ms·
On Swift’s array semantics
- andymoe 12y agoThen 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.
- blahedo 12y agolet/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.
- masklinn 12y agolet v var has to do with mutable bindings, value mutability within the bindings should be a separate concern. 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
- alexatkeplar 12y agoI 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).
- keeperofdakeys 12y agoThe 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.
- jakejake 12y agoI'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.
- tl 12y agoI'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.
- egwor 12y agoagreed; 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
- tel 12y agoThis 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.
- josephlord 12y agoI 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.
- tel 12y agoIf 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.
- clscott 12y agoThe 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: let a = [[1,2,3],[6,7,8]]
- nemetroid 12y agoCan you give an example for which the proposed syntax would be problematic? Your example would be written as: let a = [: [: 1, 2, 3 :], [: 6, 7, 8 :] :] or let a = [ [: 1, 2, 3 :], [: 6, 7, 8 :] ] etc., depending on what mutability you want.
- binarycrusader 12y agoThe actual article title is currently: "Design mistakes in Swift language’s array"
- dang 12y agoWe 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.
- ryanpetrich 12y agoThe proposed syntax creates ambiguity between immutable arrays and mutable arrays containing only a single mutable array.
- skrebbel 12y agoA 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".
- mostly_harmless 12y agoThis 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
- masklinn 12y ago> 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: No. The proposal is similar to the following C: int const * i;
- josephlord 12y agoI 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.
- josephlord 12y agoMuch too late to edit. I should have said "...assign, pass to a function..."
- ddp 12y agoI 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.
- blahedo 12y agoI 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.
- deleted 12y ago[deleted]
- meowface 12y agoI agree. 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.
- deleted 12y ago[deleted]
- deleted 12y ago[deleted]
- tel 12y agoI 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.
- kipple 12y agoAccording 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? https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html 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.
- RayDonnelly 12y agoNo. As stated in the article, the array was not copied when it was assigned to var b as changes made to it via b also showed up in a.
- js2 12y agoWhich was a bug in beta 2 that was corrected in beta 3. So the behavior in beta 3 matches the design quoted above. Hence kipple is correct.
- josephlord 12y agoIt was a design bug not a implementation bug. 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 https://devforums.apple.com/message/989944#989944 [1] http://blog.human-friendly.com/swift-arrays-too-swift-and-flexible-for-our-good http://blog.human-friendly.com/swift-arrays-too-swift-and-fl...
- twic 12y agoA similar but more subtle problem happened to Java’s arrays regarding aliasing and subtyping a long time ago, which could crash the JVM. 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.