Rust and Swift (vi)

Collection types and the difference between syntax and semantics.

September 19, 2015Filed under Tech#rust#rust-and-swift#software development#swiftMarkdown source

I am reading through the Swift book, and comparing it to Rust, which I have also been learning over the past month. As with the other posts in this series, these are off-the-cuff impressions, which may be inaccurate in various ways. I’d be happy to hear feedback! Note, too, that my preferences are just that: preferences. Your tastes may differ from mine. (See all parts in the series.)

It kind of feels like this summarizes a lot of things about the overall design of Swift:

Although the two forms are functionally identical, the shorthand form is preferred and is used throughout this guide when referring to the type of an array. —The Swift Programming Language (Swift 2 Prerelease)

The documentation for the various types in Rust’s std::collections module is hilarious and great. Highly recommended.

One thing that jumped out at me reading this chapter of the Swift book (though I don’t think it’s been explicitly discussed yet): Rust doesn’t have named parameters; Swift does. There are good reasons for that in both cases, but I suspect this is one of the small details I’ll miss the most in Rust. I’ve been spoiled by Python.

Swift’s Array type is analogous to Rust’s Vec type (usually created with the vec! macro), not its Array type. Rust Vecs and Swift Arrays are dynamically sized and created on the heap, whereas Rust’s Arrays are statically sized and created on the stack. Syntax for creating Arrays in both languages is quite similar (though the results are different):

  • Swift:
    • Fixed size: let an_array: [Int] = [1, 2, 3]
    • Variable size: var an_array = [1, 2, 3]
  • Rust:
    • Array: let an_array: [i32, 3] = [1, 2, 3];
    • Vector: let a_vector: Vec<i32> = vec![1, 2, 3];

That’s the long form, of course; both languages have type inference, so you’d rarely write it like that. The usual form would be with the type in all of those cases:

  • Swift:
    • Fixed size: let an_array = [1, 2, 3]
    • Variable size: var an_array = [1, 2, 3]
  • Rust:
    • Array: let an_array = [1, 2, 3];
    • Vector: let a_vector = vec![1, 2, 3];

Rust also adds the concept of “slices,” which provide access to segments of arrays, and are heap-allocated as pointers to a given item in the array and a length (number of elements) included.

Array operations in Swift are all pretty reasonable, and surprisingly descriptive. They remind me in a good way of Python’s list methods.

There are a lot of ways to interact with Vecs in Rust. (That’s not a bad thing.) A bit surprising to me was the absence of an enumerate method, on Vec itself, but then I discovered that it exists in the IntoIter struct in the same module, which fully implements the Iterator trait. As a result, it has an enumerate function returning an Enumerate struct instance. (Under the covers, I suspect Swift Arrays just implement an Iterable protocol, which is similar to this approach in some ways.)

This makes a point I’m coming back to fairly often: Rust doesn’t necessarily put everything on a single object definition, but rather into a set of related struct or enum types and traits. This is really powerful, but it’s going to take some mental adjustment. In this way, Swift’s structure and semantics are much more like the languages I’m used to than Rust’s are (but even there, the use of protocols gives it considerable new flexibility).

Note that I said semantics, not syntax. Swift and Rust are a great example of how very similar syntax can mask differences in semantics. (For another such example, compare JavaScript’s syntax and semantics to Java’s: they’re superficially similar syntactically, and light years apart semantically.)

Swift’s Set type and Rust’s roughly analogous HashSet both have a contains method which behaves much like Python’s in keyword. Indeed, and perhaps unsurprisingly, the two types implement many of the same methods in general. This is perhaps to be expected given that the language around sets (as a mathematical concept being mapped down into a representation in a program) is quite standardized.

Because of their stricter typing systems, both Rust and Swift require you to specify the types used in their mapping constructs (Rust has HashMap and Swift has Dictionary), though of course both can infer this as well in certain cases. At the most basic level, you can’t use arbitrary (hashable) types as keys in mixed fashion like you can in e.g. Python’s dict type, but in practice this shouldn’t matter, for two reasons:

  1. It’s generally inadvisable to use different types for keys in the same dictionary anyway. To me, at least, that usually indicates the need to step back and think more carefully about the types and data structures I’m using.
  2. For the occasional case where it is appropriate, I wonder if you could declare the type as generic in either Rust or Swift. I’m putting this down as a TODO item for myself to find out!

I really wish that Swift used the Python-style curly-brace delimited syntax ({'key': 'value'}) for its dictionary literal initializers. I can see, from a syntax reason, why it doesn’t: that would overload the block syntax (which Python can avoid because it’s white-space delimited). But it’s really convenient.

Along similar lines, I can see why the Swift designers chose to make all iterables have literal initializers using braces ([...]); it makes parsing fairly straightforward. However, the result is that it’s pretty difficult to see at first glance what you’re dealing with. It could quite easily be an Array, a Set, or a Dictionary.

This highlights a too-little-appreciated aspect of programming language design: readability. However much we programmers enjoy writing code, the reality is that we will all spend a great deal of our time—probably even a majority of it—reading it instead. Thus, while we should care about conveniences for writing code, and being overly verbose can be a pain, we should also concern ourselves with the ease of comprehending code when it is read, and the syntax and conventions a language embraces are a big part of this.

The Dictionary type in Swift is a pretty close analog to Python’s dict, right down to several of the method names. the same is true of Rust’s HashMap. That’s not a bad thing by any stretch of the imagination.