Is Swift becoming unergonomic Rust?

(No, but I thought it was)

drawing of a deer, talking to you.

Going to preface this post with this: I'm not a programming language designer. I don't really know what I'm talking about. I'm just a Rust developer with a passing interest in Swift. I probably made some mistakes in here, feel free to send me corrections.

So about a week ago, I was bored and watching WWDC 25 (pronounced "dub dub DC", apparently.) I was watching this video: Improve memory usage and performance with Swift, where they show off some new features in Swift to reduce memory overhead and increase safety (feel free to watch this alongside!) The first new standard library construct they show off is InlineArray<N, T>, which acts like... an array. In Rust terms, the normal Swift array is similar to a Arc<Vec<T>>, where it is a growable collection that can be shared across threads and is copied on write. To keep the analogy going, the new Swift InlineArray is like a Rust array: [T; N]. To make it clear, Array is heap-allocated and InlineArray is stack-allocated.

And let's compare to Rust:

fn vec() {
    // I could wrap this in an Arc, but
    // that's not really what real code would do.
    let mut vec = vec![4, 5, 6];
}

fn array() {
    let mut arr = [4, 5, 6];
}

You can probably see the argument I'm going to make here — This is less ergonomic. Rust is often said to have bad syntax, but here you can see that the better performing option is just easier than the heap-allocated version.

Safety

So another topic in the video is how to efficiently read from a reference counted collection. In order to avoid the reference counter, and read data directly, one would previously use an unsafe pointer to do a direct read.


    // Safe usage of a buffer pointer
    func processUsingBuffer(_ array: [Int]) -> Int {
        array.withUnsafeBufferPointer { buffer in
            var result = 0
            for i in 0..<buffer.count {
                result += calculate(using: buffer, at: i)
            }
            return result
        }
    }
    
Example from above WWDC talk, used as a reference.

As highlighted in the talk, these buffers are unsafe partially because they could be used outside of the scope of the function (e.g. returned, or stored as part of a structure), and misued. In order to make this operation safe, they introduce a type called Span, which points to a contiguious block of memory. In order to make this safe, a new "Marker trait" (Rust speak) called Escapable is added to the language. This trait is auto-added to all types by default, and you can specify if your type cannot be used outside of its context with ~Escapable. Pretty neat language feature!

This is a very different approach to Rust. We deal with pointer unsafety by relegating them to Unsafe Rust, a language that lives alongside Safe Rust. But also — We sidestep this whole problem. Vecs are not reference-counted (you have to wrap them in a Rc or Arc for that,) so we don't have to sidestep the reference counter.

Conclusion

You can clearly see the difference in these language's designs from these two examples. Swift makes working with types as easy as possible, while letting you "drop down" for performance. Rust instead makes the most ergonomic pattern the most performant, while letting you "build up" convenience by moving to heap allocation with Vec<T>, adding copy-on-write with Cow<T>, or reference counting with Rc<T>.

I think it's clear who these language features are for — Swift library maintainers. Being able to write more performant code for your users will make everyone happy, and doing this without unsafety is nice. I don't think this is really for app developers, or anything like that. Making all of your array accesses use spans to dodge the reference counter is technically faster, but makes things much more annoying to maintain than the obvious solution. I think the talk makes this clear, as they show off a new Binary Parsing library at the end which uses these new language features.

As for the title of this article. I don't think Swift is becoming unergonomic Rust (Betteridge's law of headlines wins again.) It's just bringing in features similar to Rust to "drop down" to. Honestly, a "less complex" Rust has a good place in the world of programming languages. There is some work adjacent to this, see the Alloy language: a fork of Rust that gives you opt-in garbage collection for things like Linked Lists. I think it's easy to write things like this off — but all languages have their tradeoffs. What makes you a good developer is being able to take all of the values of prospective systems and being able to pick the right one!

Footnotes

  1. So Swift calls traits Protocols, and this one is implicitly applied to all Swift types. Here's SE-0446 if you want to read into it further. In Rust, we would make a trait, and then have a blanket implementation over a generic. Dunno how Swift does this!
  2. Bryan Cantrill has a great talk on this: Platform as a Reflection of Values