17 comments

[ 5.7 ms ] story [ 58.7 ms ] thread
> In Rust it's also possible to copy values as a way to silence borrow checking errors, but this does add visual noise as copies have to be explicitly specified.

This statement isn't quite accurate. Types that can be safely duplicated by memcopy generally implement the Copy trait, which allows the compiler to do the same kind of silent copying that the author describes for Swift.

Cloning, on the other hand, must be explicitly specified; the difference here is that it may involve some amount of computation to make the logical copy in addition to simply copying the bytes.

> Value types, by contrast, are stack-allocated in general, and their memory is managed statically. However, the behavior of value types in Swift is much different to how Rust handles memory.

Yep. The only difference in that article is that it says that they are stack-allocated in general. I would be more interested to know the difference between what can be copy in Rust vs. the a Value type is swift.

In Rust user-defined types are copyable only if they explicitly opt-in. Even trivial tiny structs can't be copied unless they have `derive(Copy, Clone)` in their declaration.

This is very helpful, because you know you can't copy something expensive by accident.

It comes handy in libraries. Zero-sized non-copyable objects with destructors can be used as lock guards/handles that control exclusive access to a shared resource.

> In Rust user-defined types are copyable only if they explicitly opt-in. Even trivial tiny structs can't be copied unless they have `derive(Copy, Clone)` in their declaration.

This is true, but it's only half the story, and i think it's the less interesting half.

As you can say, you can make a type copy by asking the compiler to derive(Copy). But the compiler will only do this if all the fields in the struct are themselves Copy [1].

Primitives (integers, floats, booleans, chars) are Copy; shared (aka immutable) references are copy; tuples and arrays containing only Copy types are Copy. Structs and enums declared to be Copy are Copy (as long as they only contain Copy fields).

But exclusive (aka mutable) references are not copy: Rust's safety guarantees depend on there only being one instance of an exclusive pointer to something (hence the name). Unique, which wraps a raw pointer, is also not Copy, because the whole point of it is to mark a pointer as unique. Unique underlies Box, Vec, and String, so those are also not Copy.

So, user-defined types are copyable only if they explicitly opt-in and it is actually safe to copy them.

As an aside, smart pointer types like Rc are also not Copy, but not for structural reasons: the one field in a Rc is Copy. But Rc itself being Copy would defeat the object of actually counting references, so it is not declared Copy.

[1] https://doc.rust-lang.org/std/marker/trait.Copy.html#when-ca...

> In Swift, value types have what's called "copy-on-write" behavior, which means every time a value type is written to a new variable, or passed to a function, a copy is made.

That isn't copy-on-write, that's just copy.

Does "copy-on-write" mean that Swift behaves as if it is doing this, but actually defers copies where it can, passing references to the original until a function needs to modify its 'copy'?

Rust lets you do this explicitly via the Cow smart pointer:

https://doc.rust-lang.org/std/borrow/enum.Cow.html

But it's not transparent, and it's not used a lot in practice.

The author is wrong in their description. Some value types in Swift (like the standard library collection types like Array<T>) are value types with copy-on-write behavior. Until modified, copies of the Array are not made.

That copy-on-write functionality must be purposely implemented in the type and is not something provided automatically in Swift.

Thanks for the feedback, I was conflating copying with copy-on-write. I have made a correction in the article.
I think the author has it right, Swift makes things easy, Rust is more challenging but better for high efficiency, embedded, etc.

I spent many evenings playing with Rust early last year, and more or less liked it, but I didn’t have a use case for really getting into it.

I have been switching prototyping between Swift and Common Lisp for a product idea I am exploring. I am used to effortless development with Lisp languages, and once I started hacking around with Swift, I found that it has few pain points and is generally a nice language. Two big advantages of Swift are the free Apple dev tools and TensorFlow for Swift keeps getting better. Additionally, Apples newest CoreML is really good.

A good example is a function like map() that applies a closure over the elements of a collection. In rust the compiler will prevent you from modifying the collection from the closure. In swift the collection author has to make the implementation do something intelligent if the collection changes. This 'intelligent' thing usually means a slower implementation. For example in Rust map() for Vector can load the address of the buffer into a register and length into a register and assume they won't change. In the swift implementation it would have to reload the pointer to the buffer and the length every time the user code returned because the Array might have been modified.
Swift arrays are value types, how would you modify the array from inside map?

    var x = [1, 2, 3];

    let y = x.map( { v -> Int in
      print("got \(v)")
      x.removeAll()
      return 1
    })

    print("x: \(x)")
    print("y: \(y)")


    got 1
    got 2
    got 3
    x: []
    y: [1, 1, 1]
I don't know much about swift but this shows the closure is able to mutate the Array. I guess value types are captured by reference by a closure.