Swift is a more convenient Rust

Swift vs Rust: A Deep Dive into Two Powerful Languages

In the ever-evolving landscape of programming languages, two names have been making waves: Swift and Rust. Both languages have garnered significant attention for their unique features and capabilities. This article explores the similarities and differences between Swift and Rust, delving into their core philosophies, syntax, and use cases.

A Tale of Two Languages

Rust: The Systems Language

Rust, a relatively new language, has quickly become one of the most loved languages among developers. Its claim to fame is its innovative approach to memory management, which solves many of the issues associated with traditional systems programming languages without resorting to slower solutions like Garbage Collection or Reference Counting.

Rust introduces the concept of ownership, a revolutionary idea that ensures memory safety without sacrificing performance. This feature, combined with its powerful type system, generics, and functional language features like tagged enums and match expressions, makes Rust a formidable choice for systems programming.

Rust’s LLVM-based compiler allows it to compile to native code and WebAssembly (WASM), making it versatile for various applications. The language also provides utilities like Rc, Arc, and Cow for reference counting and “clone-on-write” semantics, and an unsafe system for accessing raw C pointers when needed.

Swift: The Multi-Paradigm Language

Swift, on the other hand, was designed to replace Objective-C and has since evolved into a powerful, multi-paradigm language. Like Rust, Swift incorporates many functional language features, including tagged enums, match expressions, and first-class functions. It also boasts a robust type system with generics.

Swift, too, offers type safety without a garbage collector. By default, everything is a value type with “copy-on-write” semantics. When more control is needed, Swift allows developers to opt into an ownership system and “move” values to avoid unnecessary copying. For low-level operations, Swift provides an unsafe system for accessing raw C pointers.

Swift’s LLVM-based compiler enables it to compile to native code and WASM, similar to Rust. This shared foundation in LLVM contributes to their similar performance characteristics and cross-platform capabilities.

Deja Vu? The Similarities Between Swift and Rust

As you delve deeper into Swift, you might experience a sense of déjà vu. The similarities between Swift and Rust are striking, with both languages sharing a core set of features and philosophies. However, the key difference lies in their perspective and default memory models.

Rust is Bottom-Up, Swift is Top-Down

Rust is fundamentally a low-level systems language that provides tools to go higher level when needed. Swift, conversely, starts at a high level and offers the ability to go low-level when required.

The most apparent example of this difference is in their memory management models. Swift uses value types by default with “copy-on-write” semantics, which is equivalent to using Cow for all values in Rust. However, Rust makes it easy to use “moved” and “borrowed” values but requires extra ceremony to use Cow values. Swift, on the other hand, makes these “copy-on-write” values easy to use and requires extra effort to use borrowing and moving instead.

This difference in default behavior means that Rust is faster by default, while Swift is simpler and easier to use out of the box.

Swift Takes Rust’s Ideas and Hides Them in C-Like Syntax

Swift’s syntax is a masterclass in taking functional language concepts and presenting them in a familiar, C-like syntax. This approach makes Swift more approachable for developers coming from C-style languages while still offering the power of functional programming.

For example, consider Rust’s match statement:

rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}

The equivalent Swift code would look like this:

swift
enum Coin {
case penny
case nickel
case dime
case quarter
}

func valueInCents(coin: Coin) -> Int {
switch coin {
case .penny: 1
case .nickel: 5
case .dime: 10
case .quarter: 25
}
}

Swift doesn’t have a match statement or expression. Instead, it uses a switch statement that developers are already familiar with. However, this switch statement is not a traditional switch at all – it’s an expression. It doesn’t “fallthrough,” it does pattern matching, and it’s essentially a match expression with a different name and syntax.

Swift takes this concept further by allowing methods to be placed directly on enums:

swift
enum Coin {
case penny
case nickel
case dime
case quarter

func valueInCents() -> Int {
    switch self {
    case .penny: 1
    case .nickel: 5
    case .dime: 10
    case .quarter: 25
    }
}

}

Optional Types: Swift’s Nil is Rust’s None

Rust doesn’t have null, but it does have None. Swift has nil, which is essentially None in disguise. Instead of an Option<T>, Swift uses T?, but the compiler still forces you to check that the value is not nil before you can use it.

This approach provides the same safety as Rust’s Option type but with more convenience. For example, in Swift, you can do this with an optional type:

swift
let val: T?
if let val {
// val is now of type T.
}

Additionally, you’re not forced to wrap every value with a Some(val) before returning it. The Swift compiler takes care of that for you, transparently converting a T into a T? when needed.

Error Handling: Swift’s Try-Catch is Rust’s Result

Rust doesn’t have try-catch. Instead, it uses a Result type that contains both success and error types. Swift also doesn’t have a traditional try-catch, but it does have do-catch and requires the use of try before calling a function that could throw.

This syntax is similar to how Rust lets you use ? at the end of statements to automatically forward errors, but you don’t have to wrap your success values in Ok(). The error handling in Swift works exactly like Rust’s behind the scenes, but it’s hidden in a clever, familiar syntax.

Rust’s Compiler Catches Problems, Swift’s Compiler Solves Some of Them

Rust’s compiler is known for catching many common problems at compile time and even suggesting solutions. A prime example of this is self-referencing enums.

Consider an enum that represents a tree. Since it’s a recursive type, Rust forces you to use something like Box for referencing a type within itself:

rust
enum TreeNode {
Leaf(T),
Branch(Vec<Box<TreeNode>>),
}

Swift, on the other hand, is more “automatic” in handling such cases. You can use the indirect keyword to indicate that an enum is recursive, and Swift’s compiler takes care of the rest:

swift
indirect enum TreeNode {
case leaf(T)
case branch([TreeNode])
}

Note that you still have to annotate this enum with the indirect keyword to indicate that it is recursive. But once you’ve done that, Swift’s compiler takes care of the rest. You don’t have to think about Box or Rc. The values just work normally.

Swift is Less “Pure”

Swift was designed to replace Objective-C and needed to be able to interface with existing code. As a result, it has made many pragmatic choices that make it a less “pure” and “minimalist” language compared to Rust.

Swift is a pretty big language compared to Rust and has many more features built-in. However, Swift is designed with “progressive disclosure” in mind, which means that just as soon as you think you’ve learned the language, a little more of the iceberg pops out of the water.

Here are just some of the language features Swift offers:

  • Classes / Inheritance
  • async-await
  • async-sequences
  • actors
  • getters and setters
  • lazy properties
  • property wrappers
  • Result Builders (for building tree-like structures, e.g., HTML / SwiftUI)

Convenience Has Its Costs

Swift is a far easier language to get started with and be productive in. The syntax is more familiar, and a lot more is done for you automatically. However, this convenience comes with trade-offs.

By default, a Rust program is much faster than a Swift program. This is because Rust is fast by default and lets you be slow, while Swift is easy by default and lets you be fast.

Based on this, both languages have their uses. Rust is better for systems and embedded programming, writing compilers and browser engines (Servo), and creating entire operating systems. Swift is better for writing UI and servers and some parts of compilers and operating systems. Over time, I expect to see the overlap between these two languages grow bigger.

The “Cross-Platform” Problem

There’s a perception that Swift is only a good language for Apple platforms. While this was once true, it’s no longer the case, and Swift is becoming increasingly a good cross-platform language.

Swift even compiles to WASM, and the forks made by the swift-wasm team were merged back into Swift core earlier this year. Swift on Windows is being used by The Browser Company to share code and bring the Arc browser to Windows. Swift on Linux has long been supported by Apple themselves to push “Swift on Server.”

This year, Embedded Swift was also announced, which is already being used on small devices like the Panic Playdate. The Swift website has been highlighting many of these projects, showcasing Swift’s growing cross-platform capabilities.

The Browser Company says that “Interoperability is Swift’s super power,” and the Swift project has been trying to make working with Swift a great experience outside of Xcode with projects like an open-source Language Server Protocol (LSP) and funding the VSCode extension.

Swift is Not a Perfect Language

Like any language, Swift has its drawbacks. Compile times are quite bad, similar to Rust. There is some amount of feature creep, and the language is larger than it should be. Not all syntax feels familiar, and the package ecosystem isn’t nearly as rich as Rust’s.

However, the “Swift is only for Apple platforms” is an old and tired cliché at this point. Swift is already a cross-platform, ABI-stable language with no GC, automatic Reference Counting, and the option to opt into ownership for even more performance. Swift packages increasingly work on Linux, and Foundation was ported to Swift, open-sourced, and made available for all platforms.

It’s still early days for Swift as a good, more convenient, Rust alternative for cross-platform development, but it is here now. It’s no longer a future to wait for.


Viral Tags and Phrases:

  • Rust vs Swift showdown
  • Memory management revolution
  • Functional programming made easy
  • Cross-platform coding made simple
  • Swift’s secret sauce revealed
  • Rust’s ownership model explained
  • The future of systems programming
  • Swift’s hidden functional features
  • Rust’s performance secrets
  • Swift’s progressive disclosure
  • The truth about Swift on non-Apple platforms
  • Rust’s compiler superpowers
  • Swift’s convenience vs Rust’s speed
  • The rise of Embedded Swift
  • Swift’s interoperability advantage
  • Rust’s bottom-up approach
  • Swift’s top-down design
  • The evolution of error handling
  • Optional types demystified
  • Recursive enums made easy

,

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *