Polyglot
Languages Swift types
Swift § types

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 transparentName 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, Bool as the conventional defaults.
  • Use type inference freely; annotate public APIs.
  • Use explicit conversionsDouble(n), String(n).
  • Use Optionals for nullability — never nil as a sentinel.
  • Use struct types by default; class for reference semantics.
  • Use let over var — immutability where possible.
  • Use tuples for small named return values; structs for substantial structures.
  • Use typealias for clarity in complex types.
  • Avoid Any and AnyObject — 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.