Learn Swift for C++ Developers
Learn how to program in Swift with side-by-side C++ examples, covering topics from variable assignment to generics.
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
= 10incut(_ str: String, len: Int = 10), making the argument optional - a different name is declared, like
ofinfirstWord(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.