Types
Swift is strongly statically typed with substantial type inference — the compiler determines types from initialisers, return values, and context. The principal types: Int, Double, Float, Bool, String, Character, plus the collection types Array<T>, Dictionary<K, V>, Set<T>, the optional Optional<T>, and the value-type struct and reference-type class. Tuples admit anonymous heterogeneous aggregation. Type aliases (typealias) admit naming existing types. Type checking is strict — implicit numeric conversions are not admitted; explicit conversion is required. The combination — strict static typing, substantial type inference, value-typed standard collections, the optional encoding of nullability — is the substance of Swift’s type system.
Basic types
The principal scalar types:
let n: Int = 42 // 64-bit signed integer (on most platforms)
let f: Double = 3.14 // 64-bit floating point
let p: Float = 3.14 // 32-bit floating point
let b: Bool = true
let c: Character = "A"
let s: String = "hello"
Int is the default integer type — its size matches the platform (typically 64-bit on modern systems). For specific sizes:
let i8: Int8 = 127
let i16: Int16 = 32_767
let i32: Int32 = 2_147_483_647
let i64: Int64 = 9_223_372_036_854_775_807
let u8: UInt8 = 255
let u16: UInt16 = 65_535
let u32: UInt32 = 4_294_967_295
let u64: UInt64 = 18_446_744_073_709_551_615
let uint: UInt = 100 // platform-dependent (matches Int's signed counterpart)
The conventional discipline is use Int unless a specific size is genuinely needed.
Numeric literals
42 // decimal
0x2A // hex
0o52 // octal
0b101010 // binary
3.14 // float (Double by default)
6.022e23 // scientific
0xFp10 // hex floating point
1_000_000 // underscore separators
1_000.000_1
Numeric literals are initially untyped — they take the type of their context:
let n = 42 // Int (default)
let f: Double = 42 // Double
let i: Int8 = 42 // Int8
let x = 0.1 + 0.2 // 0.30000000000000004 (the conventional Double pitfall)
For exact decimal arithmetic, the Decimal type (from Foundation):
import Foundation
let d = Decimal(0.1) + Decimal(0.2) // 0.3 (exact)
Type inference
Swift admits substantial type inference:
let n = 42 // Int
let f = 3.14 // Double
let s = "hello" // String
let arr = [1, 2, 3] // [Int]
let dict = ["a": 1, "b": 2] // [String: Int]
let nilable: Int? = nil // explicit (cannot infer Optional)
// In function returns:
func compute() -> Int {
return 42 // return type known
}
// In closures:
let double = { (n: Int) in n * 2 } // closure type: (Int) -> Int
The conventional discipline:
- Type inference for local bindings with clear initialisers.
- Explicit annotations for public APIs (function signatures, properties).
- Explicit annotations when the inferred type may be wrong (e.g.,
[1, 2.0]infers[Double]).
Type conversion
Numeric conversions are always explicit:
let n: Int = 5
let d: Double = n // ERROR: cannot assign Int to Double
let d: Double = Double(n) // OK
let f: Float = 3.14
let i: Int = Int(f) // 3 (truncation)
// String / number:
let str = String(42) // "42"
let n = Int("42") // Int? (returns nil on failure)
let d = Double("3.14") // Double?
let n2 = Int("abc") // nil (fails gracefully)
The strictness produces substantial safety; the conventional defence is to use Optional returns from Int(_:) and Double(_:) for parsing.
Tuples
Anonymous fixed-size aggregations:
let pair: (String, Int) = ("Alice", 30)
let triple: (Int, Int, String) = (1, 2, "three")
// Destructuring:
let (name, age) = pair
print(name, age) // "Alice 30"
// Named elements:
let person: (name: String, age: Int) = (name: "Alice", age: 30)
print(person.name, person.age)
// Or by index:
print(pair.0, pair.1)
Tuples admit substantial conciseness for “named return values” and similar patterns:
func divmod(_ a: Int, _ b: Int) -> (quotient: Int, remainder: Int) {
(a / b, a % b)
}
let result = divmod(17, 5)
print(result.quotient, result.remainder) // 3 2
// Or destructure:
let (q, r) = divmod(17, 5)
Tuples are value types; assignment copies all components.
For substantial structures, structs are conventionally clearer.
Arrays
Ordered collections; type-parameterised:
var arr: [Int] = [1, 2, 3]
var arr: Array<Int> = [1, 2, 3] // equivalent verbose form
var empty: [Int] = []
var empty = [Int]()
var empty = Array<Int>()
arr.count // 3
arr.isEmpty // false
arr[0] // 1 (crashes on out of bounds)
arr.first // Optional(1)
arr.last // Optional(3)
arr.append(4) // mutate
arr += [5, 6] // append multiple
arr.insert(0, at: 0)
arr.remove(at: 0)
let doubled = arr.map { $0 * 2 } // [2, 4, 6, 8, 10, 12]
let evens = arr.filter { $0 % 2 == 0 }
let sum = arr.reduce(0, +)
Arrays are value types — assignment copies (with copy-on-write optimisation). Treated in Data structures.
Dictionaries
Hash maps; type-parameterised:
var scores: [String: Int] = ["Alice": 95, "Bob": 87]
var scores: Dictionary<String, Int> = ["Alice": 95]
scores["Charlie"] = 78 // add or update
scores["Alice"] = nil // remove
if let alice = scores["Alice"] { // returns Optional
print(alice)
}
scores.count
scores.keys // collection view
scores.values
Dictionary access returns Optional<V> — the conventional unwrapping is if let or nil-coalescing:
let alice = scores["Alice"] ?? 0
Dictionaries are value types. Treated in Data structures.
Sets
Unordered unique collections:
var fruits: Set<String> = ["apple", "banana", "apple"]
print(fruits) // ["apple", "banana"] (deduplicated)
fruits.insert("cherry")
fruits.contains("apple")
fruits.remove("banana")
let a: Set = [1, 2, 3]
let b: Set = [2, 3, 4]
a.union(b) // {1, 2, 3, 4}
a.intersection(b) // {2, 3}
a.subtracting(b) // {1}
Sets are value types. Treated in Data structures.
Optionals
The most distinctive Swift type — Optional<T> (or T?):
var name: String? = nil // optional; may be nil
name = "Alice"
name = nil // OK
var name: String = nil // ERROR: non-optional cannot be nil
let length = name?.count // optional chaining
let length = name!.count // force unwrap
let length = name?.count ?? 0 // nil-coalescing
Treated in Optionals.
Type aliases
The typealias declares an alias for an existing type:
typealias Name = String
typealias Age = Int
typealias Person = (name: Name, age: Age)
typealias Callback = (Result<Data, Error>) -> Void
let p: Person = ("Alice", 30)
let cb: Callback = { result in /* ... */ }
Type aliases are transparent — Name is exactly String. For nominal (distinct) types, struct wrappers are conventional:
struct UserID {
let value: Int
}
struct ProductID {
let value: Int
}
// UserID and ProductID are now distinct types — no accidental swap.
Any and AnyObject
Any admits values of any type (including value types):
var anything: Any = 42
anything = "hello"
anything = [1, 2, 3]
// Type checking:
if let n = anything as? Int {
print("got an int: \(n)")
}
// Cast:
let s = anything as! String // force cast (crashes on failure)
AnyObject admits values of any class type (reference types only):
class Cat {}
class Dog {}
let pets: [AnyObject] = [Cat(), Dog()]
The conventional contemporary discipline avoids Any and AnyObject — generics, protocols, and explicit types admit substantial type safety.
Type aliases for closures
typealias HTTPHandler = (Request) -> Response
typealias Predicate<T> = (T) -> Bool
func filter<T>(_ items: [T], _ pred: Predicate<T>) -> [T] {
items.filter(pred)
}
The form admits substantial readability for complex function-typed parameters.
Self
The Self keyword refers to the conforming/inheriting type:
extension Numeric {
func doubled() -> Self {
self + self
}
}
let n = 5
n.doubled() // 10 (Int)
let d = 3.14
d.doubled() // 6.28 (Double)
The Self admits substantial fluent API patterns; conventional in the standard library.
Numeric overflow and safety
Swift’s arithmetic traps on overflow by default:
let n = Int.max
let m = n + 1 // traps (crashes in debug; UB in release with optimisations off)
For wrap-around arithmetic, the explicit &+, &-, &* operators:
let n = Int.max
let m = n &+ 1 // Int.min (wraps)
The mechanism admits explicit “I want overflow” semantics; the default trapping eliminates substantial classes of integer bugs.
Range
Ranges represent spans:
let r1 = 0..<10 // half-open (0, 1, ..., 9)
let r2 = 0...10 // closed (0, 1, ..., 10)
let chars = "a"..."z" // ClosedRange<String>
for i in 0..<5 {
print(i) // 0, 1, 2, 3, 4
}
// One-sided ranges:
let a = arr[0...] // from 0 to end
let b = arr[..<3] // up to (not including) 3
let c = arr[...] // entire array
Treated in Data structures.
A note on the conventional discipline
The contemporary Swift type advice:
- Use
Int,Double,String,Boolas the conventional defaults. - Use type inference freely; annotate public APIs.
- Use explicit conversions —
Double(n),String(n). - Use Optionals for nullability — never
nilas a sentinel. - Use struct types by default; class for reference semantics.
- Use
letovervar— immutability where possible. - Use tuples for small named return values; structs for substantial structures.
- Use
typealiasfor clarity in complex types. - Avoid
AnyandAnyObject— use generics or specific types. - Use type-parameterised collections —
[T],[K: V],Set<T>.
The combination — strict static typing, substantial type inference, explicit numeric conversions, value-typed collections, the optional encoding of nullability, the Range and Sequence protocols — is the substance of Swift’s type system. The discipline produces concise, type-safe code with substantial compile-time guarantees.