I first learned Swift in 2017, which for me was ages ago. Back then it was at version 3.1. I'm much more familiar with Rust, having been studying and using it now for about 8 months. So when I went back to check out Swift again this week, I was really surprised by three things:
- Swift is now version 5.3 (two major versions!)
- Swift has finally been ported to Windows!
- Wow, Swift code sure looks a lot like Rust code!
I felt it might be fun to sit down and take notes while reading through the official Swift Book to "freshen up" on the language. Being very much into Rust, I thought it would also be fun to include in my notes any comparisons I could draw between Swift and Rust. It took a few days, and by the end I had quite a few notes written down. So I decided to clean them up a bit and post them here!
The following is essentially a boiled-down comparison of Swift (version 5.3) with respect to Rust (edition 2018).  It follows along the chapters of the official Swift Book.  Swift and Rust are very similar, so in general, if a specific point of comparison isn't mentioned here, it may be that the two languages are the same on that point, or "close enough" that I felt it didn't matter.  For example, true and false are the same for both Swift and Rust, and the syntax for various kinds of string literals are largely the same, so I didn't bother to write them down.  On the other hand, something not listed here could be something I didn't consider or know about at the time, in which case please let me know in the comments.  Thank you in advance!
If any information below is not completely verified or tested, a question-mark ? punctuation will be used.  This is an open invitation to anyone who can verify the information (whether correct or incorrect) and provide evidence one way or another.
First of all, I found a few general topics of interest that weren't covered directly by the Swift Book. I grouped them together to list here:
- 
Swift Package Manager — analogous to cargo in Rust, being a standard package management system for the language.
- Note that unfortunately, as of the time of this writing, the Swift Package Manager is not available for Windows, so ad-hoc solutions based on tools such as CMake and Ninja are often used to construct build systems for Swift projects on Windows instead.
 
- 
Swift Standard Library — this is tightly coupled with the compiler and facilitates the runtime and various types and functions essential to various aspects of the language.  It also provides support for features not covered by the book:
- 
Serialization — a set of protocols analogous to serde:
- 
Encodable—serde::Serialize
- 
Decodable—serde::Deserialize
- 
Encoder—serde::Serializer
- 
Decoder—serde::Deserializer
 
- 
- C Interoperability — various types and functions analogous to Rust's Foreign Function Interface (FFI).
 
- 
Serialization — a set of protocols analogous to serde:
- 
Foundation — this library originates from the Apple ecosystem (iOS, macOS, etc.) and is typically bundled with Swift due to providing access to essential operating system services, which in Rust are typically either built into the Rust standard library or are available from the Rust community's crate registry:
- Date and time management
- Localization (formatting of locale-aware string representations)
- File system — creating, reading, writing, or examining files and directories
- JSON encoding/decoding
- Processes and threads
- Networking
 
- 
Dispatch (also known as "Grand Central Dispatch" (GCD) — this library originates from the Apple ecosystem (iOS, macOS, etc.) and is typically bundled with Swift due to providing facilities for asynchronous programming, analogous to futures/async in Rust along with futures-rsand runtimes such astokio,async-std, andsmolavailable from the Rust community's crate registry.
TL;DR — Overall Differences
I wrote quite a few notes down here, perhaps too many for some people. So here's a summary of "what's important" when comparing Swift and Rust:
- Swift broadly categorizes types into "value" and "reference" types, hiding most of the implementation details of references from the programmer. Rust makes references a first-class type and opens the entire type system up for the programmer, for better or worse.
- Swift supports class inheritance, while Rust does not.
- Swift uses the throwing and catching of exceptions for error handling, while Rust encourages the use of enumerated types to return "success or failure" variants from fallible operations.
- Most code constructs in Rust are expressions, whereas Swift has a more traditional separation between "statement" and "expression".
- Unlike Rust, Swift offers a comprehensive "properties" feature set (i.e. computed properties, property observers, projected values) similar to C#.
- Many language features that have their own keywords and syntax in Swift are handled through Rust's type system (i.e. optional types, boxing, references, operator overloading).
- Rust includes direct syntax for asynchronous programming (async,await), while Swift does not, relying on adjunct library facilities to support it.
Opinion Section
I've tried to remain as objective as possible while writing this post. However, there were a few subjective things I felt during this process. Skip to the next section unless you're comfortable with a bit of biased opinions.
- Both languages aim to be general purpose systems programming languges. However, Swift has solid Apple roots and thus suffers from two main weaknesses:
- Although Swift has strong corporate (Apple) backing, its overall community is weak in comparison to Rust.  Being relatively unencumbered, Rust has a more "well-rounded" base and robust community, as evidenced by:
- Wider range of target platforms which support it.
- Easier procedure to obtain, set up, and operate its toolchain.
- Richer online environment for publishing and reusing libraries.
 
- While Rust includes more advanced features such as threading, networking, and asynchronous coding directly in its standard library, Swift leverages adjunct libraries such as Foundation and GCD which were designed for Apple's ecosystem specifically. Supporting other platforms entails "porting" these libraries after the fact or risking "crippling" the language for platforms which don't support the Apple libraries.
 
- Although Swift has strong corporate (Apple) backing, its overall community is weak in comparison to Rust.  Being relatively unencumbered, Rust has a more "well-rounded" base and robust community, as evidenced by:
- Rust and Swift both aim to be "safe", referring to language design and compiler features which prevent and/or detect errors at compile-time.  However, they have fundamentally different approaches:
- Rust has invested in the "borrow checker" approach where references are first-class types and the concept of "lifetimes" rises to the conscious level of the programmer.
- Cost: more difficult for the programmer to learn, due to this approach being non-traditional and the syntax being more elaborate, requiring more practice and different ways of thinking about and designing code.
- Gain: faster, more efficient code which can leverage both the stack and heap, allowing more sophisticated zero-copy designs and optimizations.
 
- Swift took a traditional "pass by copy or reference" approach, with references being inherently reference-counted.
- Gain: very similar to traditional programming languages, with semantics already familiar to most programmers, making the language easier to learn and use.
- Cost: since the more traditional approach is taken, many of the more traditional drawbacks remain, such as unnecessary copying and indirection; although arguably the compiler has opportunities to optimize at least some of these away.
 
 
- Rust has invested in the "borrow checker" approach where references are first-class types and the concept of "lifetimes" rises to the conscious level of the programmer.
- In the realm of Object-Oriented Programming, Rust takes a heavy-handed approach encouraging the Composition over inheritance principle by not supporting inheritance at all. Although Swift supporting "protocols" (traits, interfaces) makes the language more "composition-friendly", it also includes class inheritance. This makes Swift friendlier to programmers used to traditional object-oriented programming techniques using inheritance, at the cost of complicated rules for things such as initializers and access control.
Now, with all that out of the way, let's focus on language comparisons drawn by following along in the Swift Book and comparing to Rust.  In many places, I will abbreviate by showing Swift vs. Rust side-by-side with an "em (long) dash" (—) between.  Sorry if the formatting isn't too great; it works for me. ¯\_(ツ)_/¯
The Basics
Fundamental types
- 
Int—isize
- 
UInt—usize
- 
Int8,Int16,Int32,Int64—i8,i16,i32,i64
- 
UInt8,UInt16,UInt32,UInt64—u8,u16,u32,u64
- 
Uint32.min,Uint32.max—u32::MIN,u32::MAX
- 
Double,Float—f64,f32- Literals can be in hexidecimal: 0xFp-2== 3.75
 
- Literals can be in hexidecimal: 
- 
Bool—bool
Optionals
- 
T?—Option<T>
- 
nil—None
- 
value == nil,value != nil—value.is_none(),value.is_some()
- Optional binding:
- 
let possible_value: Int? = Int("123")—let possible_value = "123".parse().ok()
- 
if let value = possible_value—if let Some(value) = possible_value
 
- 
- You can have many optional bindings in a single conditional:
- if let first = Int("4"), let second = Int("42"), first < second
 
- Unwrapping values (!—unwrap()):- 
let value: Int = Int("123")!—let value = "123".parse().ok().unwrap()or justlet value = "123".parse().unwrap()
 
- 
- "Implicitly unwrapped optionals" are types that are unwrapped automatically if used in a non-optional context, but left optional otherwise:
- 
let x: Int! = Int("123")<-- adding!to type makes it an implicitly unwrapped optional
- 
let y: Int = x<-- implicitly unwrapped
- 
if let z = x<-- left optional, tested in conditional
 
- 
Constants and Variables
- "Constant" is an immutable value, with the same syntax:
- 
let name = 10—let name = 10
 
- 
- "Variable" is a mutable value:
- 
var name = 10—let mut name = 10
 
- 
- You can have multiple declarations on a single line:
- var x = 0, y = 1, z = 2
 
- Type annotations are the same, except you can define multiple values that share the same type annotation:
- var x, y, z: Int
 
- Constant and variable names can contain almost any character:
- let π = 3.14159
- let 你好 = "你好世界"
- let 🐶🐮 = "dogcow"
 
- No name shadowing allowed
- 
printis a combinationprintln!andprint!where it behaves likeprintln!by default but you can set a customterminatorargument to""to be likeprint!or something else.
- Interpolation is intrinsically supported for strings:
- print("The answer is \(add(40, 2))")
- Note that the interpolation can be any expression, including literals and function calls.
 
Comments
- Comments are the same, including ability to nest /* */style comments.
Semicolons
- Swift only requires a semicolon at the end of a statement if another statement follows it on the same line.
Type casts
- 
(Double)name—name as f64
- Some type casts return optionals:
- let possible_number: Int? = Int("123")
 
Type aliases
- 
typealias AudioSample = UInt16—type AudioSample = u16
Tuples
- Tuples are the same except you can also name the parts of tuple literals:
let status = (statuscode: 200, description: "OK")
print("Status: \(status.statuscode) (\(status.description))")
Error Handling
Here we have the first fundamental difference between Swift and Rust.  In Rust, errors are handled by representing them as values of the Result::Error type variant and returning them up the call chain through Result values.  In other words, successful return values and error return values essentially follow the same path out of function calls.  In Swift, errors are represented by values conforming to the Error protocol (trait) that are "thrown" at one point and "caught" (or not) somewhere up the call chain.  They follow a fundamentally separate path from successful return values.
A function that can throw an error must have the keyword throws placed in its declaration (before the body):
- 
func can_throw() throws {vsfunc does_not_throw() {
When calling such a function, you must prefix the call with the try keyword:
- try can_throws()
To automatically catch an error at the call site and convert it to an optional, use try? (equivalent to Result::ok() on the return value of a function in Rust):
- 
let x: Int? = try? something()—let x: Option<isize> = something().ok()
To automatically catch an error at the call site and terminate with a runtime error, use try! (equivalent to Result::unwrap() on the return value of a function in Rust):
- 
let x: Int = try! something()—let x: isize = something().unwrap()
Throw errors with the throw statement:
- throw some_error
Unless caught at the call site with try? or try!, errors "unwind the stack" or cause code execution to return out of functions until explicitly "caught". You catch errors outside the call site using a "do-catch" statement:
do {
    try something()
} catch some_error {
    handle_specified_error(some_error)
} catch {
    handle_unspecified_error(error)
}
Note that the special name error is available in the unspecified error handling block.
Assertions and Preconditions
- 
assertandpreconditionare like theassert!macro, except thatassertis only executed for a "Debug" build.
- 
preconditionFailureis essentially apanic!.
Basic Operators
- Swift has the ternary operator a ? b : c, unlike Rust where you would instead use a conditional expressionif a { b } else { c }.
- 
Stringtype implements the+operator for concatenation; in Rust the+operator (orString::push_str) requires the right-hand side to be a slice.
- 
Booltype does not support comparison, unlikeboolin Rust.
- "Nil-Coalescing Operator": a ?? b—a.unwrap_or_else(|| b)- Note that bis not evaluated unlessaisnil, which is why it maps toOption::unwrap_or_elserather thanOption::unwrap_or.
 
- Note that 
- Range operators
- Closed range: 1...5—1..=5
- Half-open range: 1..<5—1..5
- One-sided range: 1...,...1—1..,..1
- There isn't a Swift equivalent of the Rust RangeFull(unbounded range..)?
 
- Closed range: 
Strings and Characters
- Swift strings are "composed of encoding-independent Unicode characters" and "built from Unicode scalar values", whereas in Rust Stringandstrare UTF-8 encoded.
- A Swift Characteris a single extended grapheme cluster (sequence of one or more Unicode scalars), whereas a Rustcharis a single Unicode scalar. For example, in Rust,"café".chars().count()is 5 and"café".len()is 6, whereas in Swift,"café".countis 4.- The letter éis a single grapheme cluster composed of the Unicode scalar "LATIN SMALL LETTER E" (U+0065), which is one byte when UTF-8 encoded, followed by the Unicode scalar "COMBINING ACUTE ACCENT" (U+0301), which is two bytes when UTF-8 encoded.
 
- The letter 
- In Swift you can modify characters in strings by concatenating Unicode scalars together.  For example, "e" + "\u{301}" == "é".
- Swift Stringhas an associated typeString.Indexrepresenting the position of aCharacterin theString.- 
Stringmethodsindex(before:),index(after:), andindex(_:offsetBy:)are used withStringpropertiesstartIndexandendIndexto constructString.Indexvalues.
- The indicesproperty ofStringin Swift iterates over characters just likestr::charsin Rust.
 
- 
- Indexing or slicing a string returns a Substringwhich is similar tostr(holds a reference to theStringit was sliced from).- let beginning = line[..<index]
- Converting SubstringtoStringis a type cast
 
- You can encode Stringto UTF-8 or UTF-16 with theutf8orutf16properties which areString.UTF8VieworString.UTF16Viewand are iterable as a sequence ofUInt8orUInt16.
- You can get the Unicode scalar values of a Stringwith theunicodeScalarsproperty which isUnicodeScalarViewand is iterable as a sequence ofUnicodeScalar.
- Multi-line strings drop indented space, which Rust doesn't do inherently, but you can get the equivalent behavior if you use the indoc crate.
- Swift strings are value types, which are copied when passed to functions. There is no type distinction between mutable and immutable strings, like there is in Rust (with Stringbeing mutable andstr(slice) being immutable).- Note that the book mentions, "Swift’s compiler optimizes string usage so that actual copying takes place only when absolutely necessary", which implies the implementation is perhaps closer to Cow<'a, str>?
 
- Note that the book mentions, "Swift’s compiler optimizes string usage so that actual copying takes place only when absolutely necessary", which implies the implementation is perhaps closer to 
- "Extended String Delimiters" are similar to "raw string literals" in that special characters can be placed in the string without invoking their effect. However, in Swift you can "manually invoke an effect" by adding a matching number of #pound signs following the\backslash escape character:- 
#"Line 1\nLine 2"#—r#"Line 1\nLine 2"#
- 
##"Line 1\nLine 2"##—r##"Line 1\nLine 2"##
- 
#"Line 1\#nLine 2"#breaks the line and is equivalent to"Line 1\nLine 2"in Swift or Rust; no way to do this with a Rust raw string literal?
 
- 
Collection Types
There are three primary collection types in Swift:
- 
Array<T>or[T](shorthand) —Vec<T>
- 
Set<T>—HashSet<T>
- 
Dictionary<Key, Value>or[Key: Value](shorthand) —HashMap<K, V>
The only ordered collection type is the Array.
- To order the keys of a Set, use thesorted()method, which returns anArrayof the keys sorted in order.
- To order the contents of a Dictionary, use thesorted()method of thekeysorvaluesproperties, which return anArrayof the keys or values sorted in order.
The keys of Set and Dictionary need to be hashable, just as in Rust.  The protocol Hashable in Swift is like the trait Hash in Rust.
Using Arrays
- 
[Int32]()—Vec::<i32>::new()
- 
Array(repeating: 0.0, count: 3)—vec![0.0; 3]
- 
Arraysupports the+and+=operators to concatenate and append, whereas in Rust you would have to start with the left-hand side and then eitherappendorextendit.
- You can use subscript syntax to replace a value at a given index, just as in Rust:
- values[4] = "apples"
 
- You can also use subscript syntax to splice:
- 
values[4...6] = ["apples", "oranges"]—values.splice(4..=6, ["apples", "oranges"].iter().cloned());
 
- 
- 
removeLast()—Vec::pop
Using Sets
- 
Set<Int32>()—HashSet::<i32>::new()
- 
let values: Set<Int> = [1, 2, 3]—let values = hashset!{1, 2, 3}(using the maplit crate)
- 
count—HashSet::len
- 
substracting(_:)—HashSet::difference
- 
isStrictSubset(of:)andisStrictSuperset(of:)are likeisSubset(of:)andisSuperset(of:)except they returnfalseif the sets are equal.
Using Dictionaries
- 
[Int32: String]()—HashMap::<i32, String>::new()
- 
[:]—HashMap::new()
- 
let dict = [1: "apples", 2: "oranges", 3: "bananas"]—let dict = hashmap!{1 => "apples", 2 => "oranges", 3 => "bananas"}(using the maplit crate)
- 
count—HashMap::len
- 
dict.updateValue("answer", forKey: 42)(ordict[42] = "answer"if the optional old value is not needed) —dict.insert(42, "answer")
- 
dict[42]—dict.get(42)
- 
dict[42]!—dict[42]
Control Flow
- Control flow uses statement semantics, so they can't be used in expression contexts.
- For example, this is valid in Rust but not Swift: let x = if y { 1 } else { 2 }(one could use the ternary operatory ? 1 : 2instead)
- Another consequence of this is that breakstatements cannot accept an argument.
 
- For example, this is valid in Rust but not Swift: 
- Swift has an additional "repeat-while" syntax similar to the "do-while" syntax in C, but lacks the loopinfinite loop andwhile letsyntax.
- Label names for loops (where a continueorbreakin a nested loop can jump out to a labelled outer loop) must begin with a quote'character in Rust, but not in Swift.
- A Swift switchis similar to a Rustmatchexcept:- The casekeyword is present in front of each arm, similar to C.
- The defaultkeyword, withoutcase, takes the place of_to match any cases not otherwise covered.
- The body of each case must have at least one statement.
- Alternative patterns are separated by comma ,rather than pipe|.
- Value binding in patterns requires placing the letkeyword before the name to bind:- 
case (let x, 0):—(x, 0) =>
 
- 
- Swift uses the wherekeyword in place ofiffor "additional conditions" (called "match guards" in Rust).- 
case let (x, y) where x == y:—(x, y) if x == y =>
 
- 
- Swift has an additional control transfer statement fallthroughyou can use as the last statement of acasein aswitchin order to mimic the "fall through" behavior of aswitchin C where the lack of abreakcauses execution to flow from the end of one case to the beginning of the next case.
 
- The 
- Swift has an additional "guard" syntax intended for use in "early exit" scenarios.  It looks simiar to an ifstatement, exceptifis replaced byguard, theelsekeyword is placed before the body, and any value bound to the condition (withguard let) is scoped to the block containing the guard. The body statements are only executed if the condition isfalse, and typically contain an "early exit" return.
- Conditional compilation in Swift is done using a special conditional you can use in ifandguardstatements, as opposed to thecfgattribute in Rust.- The only special conditional mentioned in the book for use in conditional compilation is the #availablecondition used to conditionally compile based on the target platform.
 
- The only special conditional mentioned in the book for use in conditional compilation is the 
Functions
- 
func—fn
- If the entire body of the function is a single expression, the function implicitly returns that expression. Rust supports this, but also allows there to be statements before the expression, which Swift doesn't support.
- Arguments are "labelled", and such argument labels are considered part of the function name.  This gives them an "Objective-C" flavor designed for compatibility with that language.
- For example: func speak(toPerson person: String, what speech: String)- The name for this function overall is considered to be speak(toPerson:what:).
 
- The name for this function overall is considered to be 
- If unspecified, the label is the same as the name:
- 
func speak(person: String, what speech: String)is the same asfunc speak(person person: String, what speech: String)
 
- 
- A label may be omitted by replacing it with an underscore (_) in the function declaration.- 
func speakTo(_ person: String, what speech: String)- In this case the function name is considered to be speakTo(_:what:)
 
- In this case the function name is considered to be 
 
- 
- Labels not omitted must be used when calling the function.
- For example: speakTo("Frank", what: "Hello!")
 
- For example: 
- Although names must be unique, labels need not be (but usually are for readability).
 
- For example: 
- Arguments can have default values specified in the function declaration by adding after the type an equals sign (=) followed by a value. If an argument has a default value, it can be omitted when calling the function.
- Functions support variadic parameters.  If the type of a parameter is followed by ..., the actual type of the parameter will be anArrayof the specified type, while callers provide the values for the array without using array syntax.- Note that a function can have at most one variadic parameter, but it doesn't necessary need to be the last parameter?
 
func average(_ numbers: Double...) -> Double {
    /* body omitted for brevity */
}
average(1, 2, 3)
average(1.2, 3.5)
- Parameters are constants (immutable) by default.
- Parameters are passed by value (copied, as in the Copytrait in Rust) if they are fundamental types, strings, or structures. Otherwise, they are passed by reference.
- Placing the inoutkeyword before a parameter type in the function declaration gives the parameter a "copy-in copy-out" semantic (which may be optimized into "pass by mutable reference", but you shouldn't count on this).- When the function is called, the parameter is copied.
- During the function call, the parameter is mutable and should be considered separate from the original value (although it may be optimized to be the same address in memory).
- When the function returns, the parameter is copied back to caller, replacing the original value.
 
- For the purpose of identifying the type of a function with no return value, Voidtakes the place of()as the type returned.
- Functions passed as parameters are declared without any keyword like func:- 
func useFunction(_ function: (Int) -> Int)—fn use_function(function: Fn(isize) -> isize)
 
- 
- Unlike in Rust, there is no distinction in Swift between "by-value" (FnOnce), "mutable" (FnMut), "immutable" (Fn), or "function pointer" (fn) closure/function types.
- A function declared inside another function is technically a closure in Swift (has access to all values in the scope of the outer function), whereas in Rust such a function is not a closure (although you could make the inner function a closure in Rust to do the same thing).
Closures
- 
{(x: Int, y: Int) -> Int in x * y}—|x: isize, y: isize| x * y
- 
{x, y in x * y}—|x, y| x * y
- Argument names can be omitted entirely if types can be implied; in this case special names formed by $followed by argument index represent each argument- 
{$0 * $1}—|x, y| x * y
 
- 
- Operator methods are probably the shortest possible syntax for a closure.  For example, names.sorted(by: >)sortsnamesusing the>operator of the type of the items innames.
- Swift has a "trailing closure" syntax where you can move a closure parameter to a block immediately after the parentheses of the function call.  For example, names.sorted(by: { $0 > $1 })can be written asnames.sorted() { $0 > $1 }instead.- The argument label for the first trailing closure is omitted. If there are additional trailing closures, they follow the first trailing closure body and are proceeded by their argument labels.
- If there are no arguments besides the trailing closure(s), the parentheses of the function call itself can be omitted as well.  For example: names.sorted{ $0 > $1 }
 
- Closures capture constants and variables from the surrounding context by reference. As an optimization, the compiler may capture them by value instead, if they are not mutated by the closure or any code executed after the closure is created.
- Closures are reference types (may be obvious, but the book has a special section to emphasize this point).
- Closures can have a "capture list" where you explicitly control what values from the surrounding context are captured and how they are captured.
- If present, the capture list comes first inside the opening curly brace ({) of the closure.
- Inside square brackets, place a comma-separated list of items, where each item is a value to capture, optionally with the weakorunownedkeyword in front to specify whether the reference should be weak or not counted at all (respectively). Each value may also be bound to a name local to the closure. For example:- 
[self]— capture valueselfby strong reference
- 
[weak self]— capture valueselfby weak reference
- 
[weak delegate = self.delegate]— capture valueself.delegateby weak reference nameddelegate.
 
- 
 
- If present, the capture list comes first inside the opening curly brace (
- A closure which "escapes" a function (called after the function returns) must be marked with @escapingbefore the closure's type in the function parameter list.
- Marking a closure parameter with @autoclosurebefore the closure's type in a function parameter list allows you to call the function and pass an expression that is automatically wrapped by a closure. In other words, the expression passed to the function is "lazily evaluated", or not evaluated until and unless the closure is called by the function.
Enumerations
- When listing variants of enums, each line must begin with the casekeyword.- Multiple variants may be declared per line, if they are comma-separated.
 
- The syntax for specifying a variant is TypeName.variantor just.variantifTypeNamecan be inferred.
- By adding : CaseIterableafter the name of an enum in its declaration (in other words, specifying that the enum conforms to theCaseIterableprotocol), Swift will add anallCasesproperty which is a collection of all the variants.
- Enum values can be printed without implementing any protocol.
- In a switchon an enum, the associated values (if any) are bound to names by placingletfollowed by a name in the case pattern, or by placing a singleletimmediately following thecasekeyword.
- As an alternative to associated types, enums can handle "raw values" by adding a colon followed by raw value type after the name of the enum.  Each variant gets a corresponding raw value, which can be either assigned by default or assigned by providing a literal value after the variant name with an equals sign (=) between the name and value. Raw value types can be characters, strings, integers, or floating-point numbers. Variants not assigned an explicit raw value get one assigned according to their position and type:- Integers default to 0for the first variant or the value of the previous variant plus one.
- Strings default to the name of the variant itself.
- Raw value type enums automatically get an initializer method that takes a raw value of the proper type and returns an optional enum value (the variant that corresponds to the given raw value, or nilif no there is no corresponding variant for the raw value).
 
- Integers default to 
- An enum variant can have an associated value that has the same enum type, as long as the variant is marked with the indirectkeyword before thecaseof the variant. This causes Swift to add indirection to the variant, just as you would have to do manually usingBox<T>in Rust to achieve the same thing.
Structures and Classes
- Both structures and classes in Swift are like structures in Rust.
- Classes have additional capabilities that structures don't have:
- Inheritance (not directly supported in Rust at all)
- Type casting
- Deinitializers (analogous to the Droptrait in Rust)
- Reference counting (analogous to the RcandArctypes in Rust)- Structures (and enumerations) are "value types", meaning they are copied when assigned or passed to functions.
- Classes are "reference types" meaning they behave as if implicitly wrapped in an Rc/Arc, where a reference is added when assigned or passed to functions, and the value is only deallocated when the reference count goes to zero.
 
 
- Use the classkeyword in place ofstructto make a class, similar to C++.
- Fields are defined (and may be initialized as well) in the type definition using the same syntax as variable declaration and assignment, including the ability to let the compiler infer types (unlike in Rust where each field's type must be explicitly defined, and initialization happens in a method or explicit structure instantiation).
- To distinquish equality from identity, Swift defines the operator ===(and inverse!==) for references, where===is likeArc::ptr_eq, returningtrueif the operands refer to the same value.
- Swift references are automatically dereferenced when used; there is no syntax for "dereferencing" or for creating a new reference (other than assigning a reference or passing it to a function). In other words, references are not first class types in Swift?
Properties
- Properties are either "stored" (as in Rust) or "computed".
- A stored property marked with the lazykeyword before its declaration is not calculated until the first time it's used.
 
- A stored property marked with the 
- A "computed" property can be defined for classes, structures, and enum types.  They are declarated similarly to properties with accessors in C# classes:
- After the type name of the property is a block containing two inner blocks, one with getbefore it and the other withsetbefore it. These inner blocks are like functions called when the property is read or written, respectively.
- The getblock is expected to compute the value of the property and return it using areturnstatement.- A shorthand syntax is supported where the inner block of the getis a single expression. In this case, that expression is evaluated to compute the value of the property, without a need for an explicitreturnstatement.
 
- A shorthand syntax is supported where the inner block of the 
- The setblock gets an value argument which is the expression assigned to the property. There are two ways of defining this value:- By placing a name in parentheses between setand its code block, the value is given that name.
- Otherwise, an implicit value named newValueis defined within the code block.
 
- By placing a name in parentheses between 
- A read-only computed property is declared by dropping the getkeyword, keeping the block of statements that would have come afterget, and dropping thesetkeyword and its code block entirely.
 
- After the type name of the property is a block containing two inner blocks, one with 
- Swift supports "property observers" which are like functions called automatically whenever a property's value is set.  They may be added for stored properties as well as inherited computed properties (for non-inherited computed properties, you put the observer code directly in the property's "setter").
- Property observers are added in a syntax similar to computed properties, with a block after the type and default value (if any) of the property, and inner blocks proceeded by the willSetanddidSetkeywords.- Both willSetanddidSetand their code blocks are optional. You can have one or both.
- The willSetblock has the same syntax as thesetblock (besides the keyword being different) and is called just before the property is set.
- The didSetblock has the same syntax as thesetblock except for the keyword and the default name of the argument provided to it beingoldValuerather thannewValue. ThedidSetblock is called just after the property is set. The argument provided is the value the property had before it was set.
 
- Both 
 
- Property observers are added in a syntax similar to computed properties, with a block after the type and default value (if any) of the property, and inner blocks proceeded by the 
- Swift supports "property wrappers" which are special structures which represent properties and provide reusable code for the getters and setters of properties.
- They are declared like normal structures but with @propertyWrapperin front.
- They must expose a computed property named wrappedValuewhich provides the getter and setter for the wrapped property.
- Property wrappers are applied to a computed property by prefixing the computed property with the name of the property wrapper with @in front (in other words, the name of the property wrapper turned into an attribute).
- Initializers provided with property wrappers can be used to set the initial value of wrapped properties.
- A wrapped property with no default value provided uses the init()initializer to initialize the property.
- A wrapped property with a single value assigned to it uses the init(wrappedValue:)initializer to initialize the property.
- Other initializers may be used by expanding the property wrapper attribute applied to the computed property to look like a function call, with labels and values provided that need to match one of the initinitializers of the property wrapper.
 
- A wrapped property with no default value provided uses the 
- They support "projected values" which are exposed as if the type containing the computed property had a second read-only computed property with the same name but prefixed by a dollar sign ($). To add a projected value, the property wrapper declares its own property namedprojectedValuewhich provides the projected value.
 
- They are declared like normal structures but with 
- Computed properties and property observers can also be applied to global and local variables, which act like stored properties of structures.
- Global variables act as if declared with the lazykeyword, in that they are not initialized until used for the first time.
 
- Global variables act as if declared with the 
- Swift supports "type properties" which are properties applying to types themselves (rather than values of a type).  These have the statickeyword added before the property declaration.- Type properties are accessed through the type name itself, as if the type was a structure.
 
Methods
- Classes, structures, and enums can have methods.
- Instance methods are like functions declared in an implblock for a Rust type, except in Swift they are placed inside the type declaration itself, similar to how methods are declared in C++.
- Methods have access to the properties of their instance as if the names of the properties were within the scope of the method (similar to C++).
- A special implicit property selfis available to methods, bound to the instance itself. This is similar to thethisvalue in C++ methods, and similar toselfin Rustimplfunctions, except that it's implicit (as if declared as&mut selffor classes or&selffor structures and enums).
- To make selfmutable for structures and enums, add themutatingkeyword in front of thefunckeyword in the method declaration.- Note that you cannot call a "mutating" method on a constant structure or enum.
- You can assign to selfin a "mutating" method to replace the value with a new one. This can be useful, for example, in enums for changing the variant.
 
- Type methods are like instance methods except the methods are called on the type itself, selfrefers to the type, not any specific value of the type, and any unqualified method or property names used in the method refer to the other type-level methods and properties of the type, rather than of any specific value of the type.- To declare a type method, add the keyword staticin front of the method declaration.
 
- To declare a type method, add the keyword 
- Class methods are like type methods, except they can be overridden in subclasses (and therefore may only be declared in classes).
- To declare a class method, add the keyword classin front of the method declaration.
 
- To declare a class method, add the keyword 
- Unlike Rust, in Swift you can have "overloaded" methods, or multiple methods with the same name but different types for parameters and/or return value.
Subscripts
- Swift "subscripts" are similar to the IndexandIndexMuttraits in Rust, allowing types to define special methods to be used if instances are indexed.
- The syntax of subscripts are a mix of function and computed property syntax.
- Their declarations begin like functions except there is no funckeyword, and the name is replaced by thesubscriptkeyword.
- They have getandsetblocks in the body, similar to computed properties.- The getblock corresponds toIndex::indexin Rust.
- The setblock corresponds toIndexMut::index_mutin Rust, except rather than returning a reference, it takes a value to be assigned, like a computed property setter.
 
- The 
 
- Their declarations begin like functions except there is no 
- Subscripts can have multiple parameters, which are separated by commas both in the declaration and usage of the subscripts.
- A "type subscript" or "class subscript" is similar to a "type method" or "class method", respectively.  They're declared like a normal subscript, but with the staticorclasskeyword in front of the declaration. They enable indexing the overall type itself.
Inheritance
As inheritance is entirely foreign to Rust, this section will compare inheritance in Swift to inheritance in C++.
- Properties can be overridden to provide custom getters, setters, or observers for any inherited property.  An overridden property has the overridekeyword added similarly to an overriden method.- A read-only property may be overridden to be read-write.
- A read-write property may not be overridden to be read-only.
 
- Properties and methods can be prevented from being overridden by adding the finalkeyword before their declarations in the superclass.
- An entire class can be prevented from being subclassed by adding the finalkeyword before its declaration.
- There are no "pure virtual" classes or methods in Swift. Use "protocols" instead.
- "Multiple inheritance" (a subclass having two or more superclasses) is not supported.
Initialization
- An "initializer" is similar to a C++ constructor; declared like a method, named init, but without thefunckeyword, and called when the type is used like a function, in order to ensure all properties of a new instance are initialized. This is roughly analogous to thenewfunction convention andDefaulttrait in Rust, except that the instance is provided through the implicitselfvalue rather than being returned.
- Default initializers are generated for each struct/class type (as long as all fields have default values and the type doesn't provide at least one explicit initializer).
- Memberwise initializers are also generated for each struct/class type (if they don't provide at least one explicit initializer). They are the same as the default initializers except they take parameters with labels matching the names of the fields of the type.
- All properties of a value must be initialized by the time the initializer returns. Properties may be initialized either in the property declaration itself, or in the code of the initializer.
- Initializers can call other initializers of the same type, to reduce duplication of code.
- Classes can have special initializers called "convenience initializers" (declared by adding the conveniencekeyword before the initializer). These are not used implicitly by subclasses, but can be used for direct initialization of values, as well as by other initializers.
- Superclass initializers are called from subclass initializers through the name super, as in:super.init().
- Initializers that are not "convenience" are referred to as "designated initializers".
- Swift enforces rules about how initializers can call other initializers in the inheritance hierarchy of a class:
- Designated initializers must call a designated initializer from its immediate superclass, if any.
- Convenience initializers must call another initializer from the same class.
- Convenience initializers must "ultimately" (either directly or through another convenience initializer) call a designed initializer.
 
- Swift enforces rules about property initialization within the inheritance hierarchy of a class:
- A designated initializer must ensure all non-inherited properties are initialized before calling a superclass designated initializer.
- A designated initializer cannot assign to an inherited property until after calling a superclass designated initializer.
- A convenience initializer must call a designated initializer before assigning to any property.
- An initializer cannot call any methods of the type, read any property values, or refer to selfas a value until after all superclass designated initializers have returned.
 
- An initializer can be marked fallable by using init?instead ofinitfor the initializer name.- Such an initializer can use a return nilstatement to indicate that initialization failed.
- Calling such an initializer produces an optional value.
 
- Such an initializer can use a 
- By using init!instead ofinit?, an initializer produces an instance that is "implicitly unwrapped".
- A superclass can require all subclasses to provide an initializer of a certain form (parameters) by declaring its own initializer with that form and adding the requiredkeyword in front.
- Default property values can be specified as closure or function calls, in order for those default values to be computed during initialization.
Deinitialization
- Swift supports "deinitializers" for classes (not structures or enums).  They are analogous to the Droptrait in Rust.
- The deinitializer is declared like an initializer with no parameters, but with the name deinitinstead ofinit.
Optional Chaining
Optional chaining in Swift refers to using the question-mark (?) operator in an expression that calls a property or method of an optional value.  It's analogous to Option::map or Option::and_then in Rust.
For example, in Swift::
let result: SomeType? = value.property?.change()
This is analogous to the following in Rust:
let result: Option<SomeType> = value.property().map(Property::change)
The way to think of it is the call chain consists of intermediate optional values, and stops if at any point an intermediate value is nil (analogous to None in Rust).
Optional chaining which ends in an assignment results in that assignment only happening if the whole left-hand side expression is not nil.
Swift automatically "flattens" the evaluated values in optional chaining, whereas in Rust it's up to the developer to select either Option::map or Option::and_then depending on whether an optional value or non-optional value is returned at any given point to avoid "nesting" options.  In other words, in Swift:
- If a non-optional value is evaluated in an optional chain, it will become optional.
- If an optional value is evaluated in an optional chain, it remains optional (it does not become something like Option<Option<T>>for example).
Error Handling
Error handling was mostly already covered in The Basics. Essentially, Swift uses an "exception throw/catch" approach for handling errors, whereas Rust uses a "return success/failure" approach instead.
Swift also supports a special code block called a "defer statement".  By adding the defer keyword in front of a nested block of code, the compiler will execute the code inside the block just before the enclosing scope is exited.  This is useful for "cleanup" code that needs to be run in the event of an error being thrown causing the current scope to unwind.  Defer statements are executed in the reverse of the order in which they're written.
Type Casting
Two additional operators are needed in the Swift type system when dealing with classes, due to the nature of class inheritance:
- The isoperator is used to determine at runtime if a value is of a certain subclass type. This is similar toAny::isin Rust.- For example: let isApple: Bool = (fruit is Apple)
 
- For example: 
- The as?andas!operators are used to "downcast" a value to a subclass type. Theas?operator returns an optional subclass value. Theas!operator returns a subclass value or triggers a runtime error if the value isn't of the subclass type. These are similar toAny::downcast_refin Rust.
Nested Types
Swift, unlike Rust, supports "nested types", which are types declared within the scope of another type. They are merely syntactic sugar for the purpose of grouping types or limiting types used in implementations from being exposed in the public interface.
Extensions
Swift supports adding computed properties, methods, initializers, subscripts, nested types, and type protocol implementations to existing types through a special syntax.  The existing type name is prefixed with the extension keyword and followed by a block containing the new properties, methods, etc. inside.  This is all syntactic sugar giving the appearance of extending a type, when in actuality the new functionality is added alongside the existing type.  It's similar to "extension traits" in Rust?
Protocols
Protocols in Swift are analogous to "traits" in Rust.
- Protocols are declared using the protocolkeyword, followed by the protocol name, followed by a block containing declarations of the requirements of any type implementing the protocol.
- Property requirements are specified like computed properties without statement blocks.
- Method requirements are specified like methods but without bodies.
- As with superclasses, protocols include the requiredkeyword with initializer requirements.
- Types implementing protocols list the protocols after the type name. If more than one protocol is implemented, they are all listed together separated by commas.
- Swift protocols can be used as types for values. Such values are analogous to "trait objects" in Rust, allocated on the heap and always handled by reference-counted pointer, although Swift hides most of these details from the programmer.
- Generic types in Swift can conditionally conform to a protocol by listing constraints on the type, similar to trait bounds in Rust.
- Swift provides automatic ("synthesized") implementations of the Equatable,Hashable, andComparableprotocols for types that adhere to certain rules specific to those protocols.- 
Equatable—PartialEq
- 
Hashable—Hash
- 
Comparable—PartialOrd
 
- 
- Protocols can be restricted for use by classes only by inheriting from the special AnyObjectprotocol.
- Protocol composition is a way to combine multiple protocol requirements for a value of some concrete type.  It's specified similarly to how multiple trait bounds are specified in Rust, except that &is used to separate multiple kprotocols rather than the+used to separate multiple traits in Rust trait bounds.
- The is,as?, andas!operators work with procotols the same way as they do for subclasses.
- For interoperability with Objective-C, Swift allows protocols marked with the @objcattribute to contain "optional requirements", or specifications of properies or methods which may or may not be implemented for a type.- Such requirements have the keywords @objcandoptionalat the front.
- An "optionally required" method is essentially an "optional function".  The ?operator can be used in a call to such a method, to use "optional chaining" to test at runtime to see if the method is available on the value before calling it:someOptionalMethod?(someArgument)
 
- Such requirements have the keywords 
- Default implementations for protocol requirements can be specified using property extensions. This makes such requirements essentially "optional" for types to implement; if a type implements it, that implementation is used, otherwise the default implementation is used.
Generics
- An extension declaration for a generic type does not repeat the declaration of the type parameters.
- Generic associated types are declared in protocols using the associatedtypekeyword (in Rust you would just use thetypekeyword). When implementing such a protocol, the associated type is inferred, unlike in Rust where you have assign it to the associated type placeholder in the implementation of the generic.- 
struct Stack<Element>: Container—type Item = Element
 
- 
- Associated type constraints are declared along with the associated type in the protocol, whereas in Rust you would apply the constraints in the implementation blocks or methods where those constraints are needed.
- Swift supports type constraint specifications on generic type parameters using whereclauses similar to Rust; however, the clauses can specify that types be equal, in addition to that types implement certain protocols. The equivalent of requiring type equality in Rust is to use "associated type bindings". For example:- Swift: func foo<I: Iterator>(it: I) where I.Item == Foo
- Rust: fn foo<I>(it: I) where I: Iterator<Item=Foo>
 
- Swift: 
- Extensions of generics may also add type constraint specifications of their own.
Opaque Types
The some keyword in Swift is used similarly to the impl keyword, particularly in the return value position, to stand for "some type that implements a protocol (trait)" where the compiler can determine the actual concrete type:
- 
-> some Shape—-> impl Shape
Automatic Reference Counting
- Weak references: weak var tenant: Person?—let mut tenant: Weak<Person>
- An "unowned reference" has the unownedkeyword before the declaration of a non-optional value. Such a reference does not participate in automatic reference counting, but unlike a weak pointer it's not "optional" (cannot benil). A "dangling" unowned reference causes a runtime error if accessed. There is no clear analogue to this in Rust;*const T, and*mut Tcome close to realizing this concept but don't match precisely?
- An "unowned optional reference" is the same as an "unowned reference" except the value is an optional type.  This makes it analogous to a *const Tor*mut Tin Rust?
Memory Safety
Essentially, Swift does a limited form of "borrow checking" by detecting "memory conflicts" where there is some overlap (multi-borrow) in referencing values. The following examples are provided by the book:
- In-out parameters: passing a value for an inoutparameter to a function which accesses the same value
- "Self": passing a value for an inoutparameter to a method of the same value
- Passing two parts of the same tuple as inoutparameters to a function
Access Control
- A "module" in Swift is a "crate" in Rust.
- Access levels are relative to module and source file.
- Swift has finer-grained access control than Rust:
- 
open— likepublic, but allows subclassing in other modules.
- 
public— essentiallypubin Rust
- 
internal— essentiallypub(crate)in Rust
- 
fileprivate— analogous topub(self)in Rust, if in the top-level module for a source file.
- 
private— analogous toprivatein C++; not applicable to Rust.
 
- 
Advanced Operators
- 
&+—{integer}::overflowing_add
- 
&-—{integer}::overflowing_sub
- 
&*—{integer}::overflowing_mul
- 
static func +—std::ops::Add
- 
static prefix func -—std::ops::Neg
- 
static postfix func !is for postfix operators (no equivalent in Rust?).
- You can define custom operators in Swift, such as: static prefix func +++.- For custom infix operators, you can define what operator precedence group they should belong to like this: infix operator +-: AdditionPreference.
 
- For custom infix operators, you can define what operator precedence group they should belong to like this: 
Closing Thoughts
If you made it here by reading through the entire post,
  🌟Congratulations!🌟
If you jumped around and just read the sections that interested you, that's fine too.  Thanks for reading, and please let me know in the comments if there are any mistakes, things I missed, and ways I can improve.
 
 
              
 
    
Top comments (5)
Thanks for the article.
Knowing Swift very well your article was like a hole book in 15 minutes. I have a good enough idea of Rust now.
Best,
Chip
Great job. I appreciate how hard you tried to keep the article objective, but I equally appreciate that you made a section for your opinions — and that you clearly delineated the sections. Very readable.
As someone who is unfamiliar with both Swift and Rust, I would say that the essay would be improved if you explicitly clarified before the heading "The Basics" that the left side of the em dash is Swift, and the RIGHT side is Rust.
You almost say that (but not quite), so I had to look up the syntax of both languages for your first few examples, just to make sure. 🙂
Question: in your opinion what would be the best learning path for an absolute beginner to programming to learn Rust?
All resources seem to assume that learners are coming to Rust from another language.
It's frustrating because I KNOW I want to work in Rust, and I am not at all interested in learning another language so I can throw it away and use it as a scaffold to let me switch to Rust.
At any rate, since I'm unable to find resources for absolute beginners, and since there are abundant resources for learning Javascript, Python, and Swift...
Would you recommend learning Swift first then sliding over to Rust?
Or would you recommend some other learning path?
Tom, you didn't ask me but still I'm giving my opinion here. An absolute beginner can learn Rust directly. It'd be difficult for sure, but then for an absolute beginner, any language would be difficult (not the same level of difficulty though). But learning one language first, then Rust ... is a more difficult path, as Rust changes the way one thinks about resources (memory, file, lock, etc).
When I started learning Rust almost 7 years back, I knew several languages including C++ (which I'm most comfortable with). Programmers who know C++ have one advantage over others, when they start learning Rust ... and it is because Rust attempts to solve the issues which are common in C++. It's designed from C++'s problems perspective. So a C++ programmer quickly understands why a certain Rust's feature is designed in a specific way. They know the context! At least that is what I felt when I came to Rust.
Really well-written. Now that Swift 6 has been released, the concurrency design is quite interesting to compare.
Omg! I’ve never seen a comparison article so detailed and yet so interesting to want to read every line one by one like this .
This is Fab_solutely awesome