Rust and Swift (vii)

Pattern matching and the value of expression blocks.

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.)

Both Rust and Swift have pattern-matching, and with what appears to be fairly similar basic behavior. (I touched on this briefly in my first post in the series.) In Rust this goes under the match construct, with matches specified like <pattern> => <expression|statement>, optionally with guards specified with if expressions. In Swift, patterns are matched using the switch construct, with matches specified like case <pattern>: <expression|statement>, optionally with guards specified with where expressions. (where is also used in Rust, but for generic constraints, not pattern match guards.)

Both languages allow you to bind names to a matched pattern: Swift with case let <name> and Rust simply by using the name in a normal destructuring expression as part of the match definition.

Edit: that’s not quite right. In Rust, you use the @ operator with the variable name you want to bind in the match.

Edit the second: I was mixed up, because Rust actually has both of those options. You can either match directly, e.g. when getting the value of an Option type: Some(value) as the pattern will bind value. But if you need to bind a specific part of more complicated data structure, the @ operator is present to let you do it in a fairly straightforward way.

Both languages allow for the use of _ as a “wildcard” in match definitions. Since match definitions in Rust use the patterns directly, the equivalent of Swift’s C-like default is simply a wildcard match pattern (_ => <-expression|statement>).

One significant difference: like its if blocks, Rust’s match blocks are expressions, so they can be assigned. I.e., you can write this:

Both languages have break statements, but in Rust they’re only used in loop constructs, while Swift (like C) uses them to escape cases as well. The Swift book gives an example of one place they’re necessary in a switch: to match a case and do nothing there (e.g. default: break). In Rust, you would simply supply an empty block for that scenario (e.g. _ => {}).

Correctly, both languages force you to match exhaustively on relevant patterns. If you’re matching an enumerated type, for example, you must handle every enumerated value. You can of course do this with wildcard patterns or with Swift’s default, but the good thing is that both languages will refuse even to compile if a given pattern isn’t matched.

Swift’s default behavior around its switch statements is sane: it does not automatically fall through into the next statement. It does let you do this, without checking the condition on the next statement (as in C), using the fallthrough keyword. Rust, by contrast, simply doesn’t allow this at all.

Both languages supply named control statements (loops, etc.), with slightly different syntax for naming them. Rust’s, curiously, shares its syntax with lifetime definitions—more on those in a future post.

I don’t believe Rust has anything quite like Swift’s guards, which allow you to leave normal or expected control flow in the main body of a block, with a secondary block for cases where the guard isn’t matched. This isn’t a huge deal, but it does fit as a nice convenience into the typical if let pattern in Swift. Basically, it just lets you elide an empty if block and supply only the else block.

Edit: a friend points out that Swift guards also require you to exit the current scope, so it’s unambiguous what you’re doing if you use them.