Swift is a great language and often used to build iOS and macOS applications. This post is designed to help programmers with C++ experience get started programming in Swift with side-by-side C++ and Swift code for the following topics:
- Fundamental Types
a. Int
b. Double
c. Boolean
d. String
e. Tuple - Print To Console
- Operators
a. Assignment
b. Arithmetic
c. Comparison
d. Optionals
e. Force Unwrap Optional
f. Boolean Logic - Strings
a. String Literals
b. Multiline Strings
c. String Interpolation
d. Substrings - Arrays
- Sets
- Dictionaries
- Control Flow
a. If Else
b. If Let
c. For In
d. While
e. Switch - Functions
- Closures
- Structs
- Classes
- Error Handling
- Enums
- Protocols
- Extensions
- Generics
Note: to keep the examples concise in this post, the C++ examples assume the standard std
namespace is used and relevant libraries like iostream
, optional
, array
, vector
, set
, map
, assert
, and tuple
are included.
The Swift examples in this post only assume Foundation
is imported.
Fundamental Types
Swift fundamental types are similar to C++ and other languages.
Int
// Swift
1
Double
// Swift
1.0
Boolean
// Swift
true
false
String
// Swift
"Swift tutorial"
Tuple
// Swift
(1, true)
Print To Console
// Swift
print("Swift examples")
Operators
Assignment
In Swift, var
is used to define a variable that allows mutation. The type annotation in Swift is often optional. The Swift example below could be written with the type annotation as var count: Int = 0
.
// Swift
var count = 0
count = 1
Alternatively, let
is used in Swift to define a variable that does not allow mutation. Swift let
is similar in some ways, but not equivalent to, const
in C++:
// Swift
let phones = 5
// Attempting mutation
// causes an error
//phones = 3
Read the following article for a more detailed overview of the differences between let
and var
in Swift, and when to use each:
Arithmetic
Swift and C++ share arithmetic syntax.
// Swift
1 + 2
1 - 2
1 * 2.0
1 / 2.0
var count = 2
count += 1
count -= 1
count *= 2
count /= 2
count %= 4
Comparison
// Swift
var count = 3
count == 2 // false
count != 2 // true
count > 1 // true
count < 1 // false
count >= 2 // true
count <= 2 // false
Optionals
In Swift, optionals are a first class citizen of the language expressed with ?
. In C++, optionals are a distinct type. The ??
operator in Swift is very similar to the value_or(_)
optional function in C++.
// Swift
var count: Int?
count // nil
count = 3
count // optional(3)
count ?? 1 // 3
count = nil
count ?? 1 // 1
Force Unwrap Optional
The !
is used to forcefully unwrap an optional in Swift, meaning the optional value is forcefully read and an error is thrown if the optional does not have a value. In C++, the optional function value()
has similar behavior.
// Swift
var count: Int?
// Unexpectedly found nil
count!
count = 1
count! // Optional(1)
Boolean Logic
Swift and C++ share boolean logic syntax.
// Swift
let value = false
let check = true
// not
!value
// and
value && check
// or
value || check
// compound
!value && (count == 1)
Strings
String literals in Swift and C++ are similar, but string manipulation and substring indexes are very different.
String Literals
// Swift
let kind = "Advanced"
let topic = "Swift"
Multiline Strings
// Swift
let description = """
Learn Swift,
for iOS and macOS!
"""
String Interpolation
Swift's implementation of string interpolation uses \()
in string literals to interpolate values and initialize a string dynamically. There is no direct equivalent in C++, with format
in C++20 having the closest functionality.
// Swift
let domain = "advswift"
let tld = "com"
let url = "\(domain).\(tld)"
Substrings
// Swift
let colors = "OYRGBIV"
colors.prefix(2) // "OY"
colors.suffix(2) // "IV"
let start = colors.index(
colors.startIndex,
offsetBy: 2
)
let end = colors.index(
colors.endIndex,
offsetBy: -3
)
colors[start...end] // "RGB"
Arrays
// Swift
var devices = [
"iPhone", "iPad"
]
devices[1] // "iPad"
// Insert
devices.append("iMac")
devices.insert("Newton", at: 0)
// Remove
devices.removeFirst(2)
devices.removeLast()
devices.remove(at: 2)
// Contains
devices.contains("iCar")
Sets
// Swift
var devices = Set([
"iPhone", "iPad"
])
// Insert
devices.insert("iMac")
// Remove
devices.remove("Newton")
// Contains
devices.contains("iCar")
Dictionaries
// Swift
var clothes = [
"pants": 2,
"shirts": 4
]
// Access
clothes["pants"] // Optional(2)
clothes["shoes"] // nil
clothes["shirts"] = 5
clothes["socks"] = 2
// Remove
clothes["pants"] = nil
Control Flow
If Else
// Swift
var swift = true
var objc = false
if swift { /* ... */ }
if swift {
/* ... */
} else if objc {
/* ... */
} else {
/* ... */
}
If Let
// Swift
var name: String? = "Swift"
if let name = name {
// branch taken if
// name is not nil
}
For In
// Swift
let values = [1,2,3,4,5]
for value in values {
/* ... */
}
for value in 0..<5 {
/* ... */
}
for value in values[...2] {
// 1, 2, 3
}
for value in values[2...] {
// 3, 4, 5
}
While
// Swift
var proceed = true
while proceed {
/* ... */
proceed = false
}
Switch
The Swift switch
statement can be used to branch based on the value of a type, including the tuple type. Each case inside of the switch statement contains the match pattern inside of ()
. The keyword break
is used to exit the switch statement and the keyword default
is used to match all other cases.
C++ has a switch
keyword. Unlike Swift, the C++ switch
statement requires cases to be constants or literals so there is no direct equivalent example.
// Swift
let outcome = (true, false)
switch outcome {
case (true, true):
/* ... */
break
case (false, false):
/* ... */
break
default:
/* ... */
break
}
Functions
In Swift the func
keyword is used to specify a function definition, arguments are specified inside of ()
, and the ->
syntax is used to specify the return type. In C++, a return type is specified first and no explicit keyword like func
is used.
Swift also has slightly different argument definition syntax. Where Swift declares a function verify
with a String
parameter name
like verify(name: String) -> Bool
, C++ declares the same function like bool verify(string name)
.
// Swift
func greet() {
print("Hello")
}
func verify(name: String) -> Bool {
return name.count > 1
}
A key difference between Swift and C++ are named function arguments. All Swift function arguments are required to be named in a function call except where:
_
is used, like incut(_ str: String, len: Int = 10)
, removing the argument name from the function call- a default value is specified, like
= 10
incut(_ str: String, len: Int = 10)
, making the argument optional - a different name is declared, like
of
infirstWord(of str: String)
, changing the required name in the function call to the declared name
The Swift functions below would be called like this:
cut("hi ho")
orcut("hi ho", len: 10)
firstWord(of: "hi ho")
The C++ equivalents would be called like this:
cut("hi ho")
orcut("hi ho", 10)
firstWord("hi ho")
// Swift
func cut(_ str: String, len: Int = 10) -> String {
if str.count > len {
return str.prefix(len-3) + "..."
} else {
return str
}
}
// Swift
func firstWord(of str: String) -> String? {
if let substr = str.split(separator: " ").first {
return String(substr)
}
else {
return nil
}
}
Closures
Closures in Swift follow function conventions except that closures and closure arguments are often unnamed. In other words, a function definition func sort(a: Int, b: Int) -> Bool { ... }
as a closure would be { (a, b) -> Bool in ... }
. The in
keyword denotes the scope in which the arguments a
, b
are defined.
Using {}
defines a closure. Using ()
defines a closure type. For example, the closure { (a, b) -> Bool in ... }
has a closure type of (Int, Int) -> Bool
. Closure types are often defined as function arguments for callback and completion handlers.
Although capitalized, the Void
keyword in a closure type definition has the same meaning, no return value, as the void
keyword in C++.
// Swift
var codes = [
"c4", "a3", "b1", "b2"
]
_ = codes.filter({ item -> Bool in
return item.prefix(1) == "b"
})
// Swift
func task(callback: (() -> Void)) {
/* ... */
callback()
}
func task(completion: (([Int]) -> Void)) {
/* ... */
completion([1,2])
}
Note: You may encounter a shorthand syntax using $
when looking at Swift closure examples. Using $
followed by a N
number like $0
is shorthand for the Nth argument in a closure. Further, the return type and return keyword can be omitted in some cases.
Consider the following Swift example:
// Swift
_ = codes.sorted(by: { (a, b) -> Bool in
return a < b
})
The Swift shorthand may look like:
// Swift
_ = codes.sorted(by: {
$0 < $1
})
Structs
Like C++, a struct
in Swift can contain variables and functions. Unlike C++, a struct
in Swift is copied on write. This means modifying a struct copies the struct and does not modify the original reference.
// Swift
struct Context {
var source: String
var iterations: Int
// Custom initializer
init(source: String) {
self.source = source
self.iterations = 0
}
// Methods
func description() -> String {
return "\(self.source): \(self.iterations)"
}
// mutating is required
// for methods that modify
// the struct's variables
mutating func increment() {
self.iterations += 1
}
}
// Swift
var context = Context(
source: "web",
iterations: 5
)
print(context.description())
context.increment()
The following example better illustrates Swift's copy-on-write mechanics:
// Swift
struct Context {
var iterations: Int
}
var context = Context(iterations: 1)
var ref = context
ref.iterations = 2
// The original struct reference
// context is not modified
ref.iterations != context.iterations
Classes
The class syntax in Swift is similar to a struct
, using the class
keyword instead of the struct
keyword. Unlike a struct
, a Swift class is not copied-on-write.
// Swift
class Context {
var source: String
var iterations: Int
init(source: String) {
self.source = source
self.iterations = 0
}
func description() -> String {
return "\(self.source): \(self.iterations)"
}
func increment() {
self.iterations += 1
}
}
// Swift
var context = Context(source: "web")
print(context.description())
context.increment()
Error Handling
Swift uses a do
catch
syntax for error handling similar to try
catch
in C++. However, Swift's error definition and function annotation are different. Swift errors are defined as enums conforming to the Error
protocol. A Swift function that can throw an error is annotated with throws
in the function definition, and the function call is annotated with try
inside of a do
clause or function annotated with throws
.
// Swift
enum ProgramError: Error {
case failed
case terminated
}
// Swift
func task() throws {
/* ... */
}
do {
try task(completion: {});
}
catch ProgramError.failed {
// Handle specific error
}
catch (let e) {
// Handle error
}
To learn more about creating custom errors in Swift, read:
Enums
An enum
in Swift is similar to an enum
in C++ with a notable difference, Swift enum cases can contain associated values. In the following example, the case
stopped(Error)
contains an associated value of type Error
. In the switch
statement, case .stopped(let error)
maps the associated value to the variable error
in the case
clause.
There is no direct equivalent in C++ for associated enum values.
// Swift
enum Status {
case initial
case complete
case inProgress
case stopped(Error)
}
var status = Status.initial
status = Status.stopped(
ProgramError.failed
)
switch status {
case .initial:
break
case .complete:
break
case .inProgress:
break
case .stopped(let error):
// Handle error
break
}
Protocols
A protocol in Swift can be applied to any type, whereas in C++ abstract classes can only be applied to classes. This enables Swift developers to use protocols as types, driving powerful use cases (see CustomStringConvertible in the Extensions section).
// Swift
protocol Tracker {
var currentTask: String? { get }
func started(task: String)
func completed(task: String)
}
Extensions
A powerful feature of Swift not available in C++ is Extensions. Extensions allow for methods and computed properties to be added to any type, including protocol types. Define an extension using the extension
keyword followed by the type name.
// Swift
extension String {
var quoted: String {
return "'\(self)'"
}
}
// Only String type was extended
print("Swift".quoted) // "Swift"
// Any Type can be extended in Swift,
// including CustomStringConvertible
// which print uses to convert types
// to a string
extension CustomStringConvertible {
var quoted: String {
return "'\(self)'"
}
}
print("Swift".quoted) // "Swift"
print([1,2].quoted) // "[1,2]"
Generics
Generics in Swift are specified inside of <>
between the function name and arguments. One or more generic types are specified in the format name: type
. The name
is then used in the argument definition as a generic type.
In the following example, T: CustomStringConvertible
means a generic type T
conforms to CustomStringConvertible
and the function quote
expects a single argument of type T
.
// Swift
func quote<T: CustomStringConvertible>
(_ x: T) -> String {
return "'\(x)'"
}
print(quote("Hi")) // "Hi"
// Extending protocols in Swift
// is powerful!
print(quote([1,2])) // "[1,2]"
Swift Tutorial For C++ Engineers
That's it! By mapping Swift concepts to C++ equivalents, you can quickly learn the fundamentals of Swift and start building iOS and macOS applications.