Rust and Swift (ix)
Sum types (enum
s) and more on pattern matching
I am reading through the Swift book, and comparing it to Rust, which I have also been learning over the past few months. 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.)
Right off the bat when looking at the definitions for Swift’s and Rust’s
enum
types, a difference pops out: the use of the keywordcase
to introduce an enum member in Swift. In one sense, this overloads that keyword, but in another sense it’s fine: pattern matching and enums go hand in hand, so the use in both cases is fairly natural. Rust doesn’t have any special syntax to designate the elements of an enum; they’re just separated by commas.I am not at all shocked to find that Swift has a variant syntax for its unit type case declarations, where a single
case
keyword precedes a list of comma-separated cases defined on a single line. (At this point, I would be more surprised not to find a variant syntax for something in Swift!)Something truly wonderful about both Rust and Swift: enumerated types aren’t just wrappers around integer values. They’re real types of their own. This is powerful.
Rust and Swift also share in having enumerated types that can hold values. The most prominent of these so far in the Swift book are optionals, the
Optional
enum type, corresponding very closely to Rust’sOption
type. Having had these for a bit in playing with Rust, and having gotten familiar with the utility of types like these while reading Maybe Haskell—a delightful book which introduces Haskell and functional programming using Haskell’sMaybe
type—I now miss them profoundly in languages which don’t have them. (Which is to say: every language I use on a regular basis professionally: C, C++, Python, JavaScript, etc.).Swift’s enum types don’t have integer values by default—but they can have them if you define a type and assign a value to each enum case at the definition. These “raw values” are distinct from the “associated values” noted just above. I expect these exist primarily for ease of interoperation with Objective-C.
Rust doesn’t have anything like this, at least that I can think of. The main place it would be useful would be for foreign function interfaces (as in Swift), and this is one of several such gaps in Rust,along with the lack of a straightforward way to map to C’sunion
types.There are trade offs in terms of adding the functionality to the language, though, as it substantially increases the complexity of what an enum value can be, I think.Edit: This was incorrect. From the Rust Reference section on Enumerations:
Enums have a discriminant. You can assign them explicitly:
enum Foo { Bar = 123, }
If a discriminant isn’t assigned, they start at zero, and add one for each variant, in order.
You can cast an enum to get this value:
let x = Foo::Bar as u32; // x is now 123u32
This only works as long as none of the variants have data attached. If it were
Bar(i32)
, this is disallowed.Initialization of Swift’s raw-valued enum type is quite similar, and pleasantly so, to Python’s initialization of enums.
In a surprising change from the usual, Swift’s syntax for binding variable names when pattern matching against an enum is more verbose than Rust’s, requiring the use of either a leading
let
on thecase
statement if all the elements are of the same type, or alet
in front of each element otherwise:var matchedValue: String let matchee = 3.14159 switch matchee { case 3.14159: matchedValue = "pi" case _: matchedValue = "not pi" }
In Rust, a matched pattern can simply bind its value directly:
let matchee = 3.14159; let matchedValue = match matchee { 3.14159 => "pi".to_string(), _ => "not pi".to_string() };
Swift has the ability to do recursive enumerations with its
indirect
type. This is conceptually interesting, but off the top of my head I can’t think of a time when this would have been useful at any point since I started programming seven and a half years ago. The book’s example of a recursive function aliasing arithmetic expressions is fine, but not particularly illuminating to me. I suspect, though, that it might make more sense if I were more familiar with pure functional programming paradigms.Edit: a friend points out:
Indirect enums are useful for recursive types in general. There are a lot of these: Lists, trees, and streams are the big ones that come to mind.
All those same lines: Rust does not have the ability to have recursive enumerations at present (or recursive
struct
types, for that matter), at least without heap-allocating withBox
along the way. You can construct such a type, in other words, but you have to be explicit about how you’re handling the memory, and it can’t be stack-allocated.For an example of a recursive enumeration type (as well as an interesting/hilarious example of how you can easily confuse the compiler if you do this wrong), see this Rust forum post.
For some discussion on stack- and heap-allocated memory in Rust, I’ll shamelessly promote my Rust podcast, New Rustacean: take a listen to e005: Allocate it where?