Polyglot
Languages Swift nullability
Swift § nullability

Optionals

The Optional<T> type is one of Swift’s most distinctive features — an explicit, type-system-level encoding of “value may be present or absent”. Where C-family languages admit nullable references (any pointer may be null at runtime, often without compile-time checking), Swift requires every nullable value to be marked as Optional<T> (typically written T?). The compiler refuses to admit operations on optionals without explicit unwrapping. The principal unwrapping forms: optional binding (if let, guard let, while let), optional chaining (?.), nil-coalescing (??), force-unwrap (!), and implicitly unwrapped optionals (T!). The combination — explicit nullability at the type level, the substantial unwrapping operators, the compile-time checking — eliminates a substantial class of nil-pointer bugs.

The Optional type

Optional<T> is an enum with two cases:

enum Optional<Wrapped> {
    case none                                      // no value
    case some(Wrapped)                             // the value
}

The shorthand T? is conventional; Optional<T> is the explicit form.

var name: String?                                  // shorthand: Optional<String>
var name: Optional<String> = nil                   // explicit form

var name: String?                                  // initialised to nil
var name: String? = nil                            // explicit nil
var name: String? = "Alice"                        // wrapped value

// vs. non-optional:
var name: String = "Alice"                         // cannot be nil
var name: String = nil                             // ERROR

Why optionals

The conventional C-family nullable pointer admits substantial bugs:

char *name = get_name();
printf("%s\n", name);                              // crashes if get_name returned NULL

Swift requires explicit handling:

let name: String? = getName()
print(name)                                         // prints "Optional(...)"; admitted but unhelpful
print(name!)                                        // crashes if nil
print(name ?? "default")                           // safe with default

The compiler refuses to admit operations on the optional without unwrapping:

let name: String? = "Alice"
let length = name.count                            // ERROR: optional must be unwrapped
let length = name!.count                           // OK; crashes if nil
let length = name?.count                           // OK; returns Int?

Optional binding (if let)

The if let admits unwrap-if-present:

let name: String? = "Alice"

if let unwrapped = name {
    print(unwrapped)                               // unwrapped is String, not String?
    print(unwrapped.count)                         // OK
}

If name is nil, the if block is skipped.

Multiple bindings:

if let a = first, let b = second, let c = third {
    // all three are unwrapped
}

// With where clause:
if let n = parseInt(input), n > 0 {
    print(n)                                       // narrowed and validated
}

Since Swift 5.7, the shorthand form admits omitting the right-hand side when it matches the binding name:

if let name {                                      // Swift 5.7+; equivalent to if let name = name
    print(name)
}

The conventional discipline uses the shorthand for the conventional same-name pattern.

guard let

The guard let admits unwrap-or-exit:

func process(input: String?) {
    guard let input else {
        return                                     // exit if nil
    }

    // input is unwrapped here, in the rest of the scope
    print(input.count)
}

The guard requires the else block to exit the scope (via return, throw, break, continue, or a function that returns Never).

The conventional Swift pattern for early-exit on missing value:

func greet(user: User?) -> String {
    guard let user else {
        return "Hello, anonymous"
    }

    // user is unwrapped; use freely:
    return "Hello, \(user.name)"
}

Optional chaining ?.

The ?. admits “access this if non-nil”:

let user: User? = getCurrentUser()

let name = user?.name                              // String?
let city = user?.address?.city                     // String?
let length = user?.name.count                      // Int?

The expression returns nil if any link in the chain is nil.

For method calls and subscripts:

let result = list?.first?.uppercased()             // String?
let item = arr?[0]                                 // T?
let updated = settings?.update(key, value)         // Void?

The ?. admits chained access without manual nil checks.

Nil-coalescing ??

let name: String? = nil
let display = name ?? "anonymous"                  // "anonymous"

let port = config.port ?? 8080
let timeout = options.timeout ?? defaultTimeout

The ?? returns the left operand if non-nil; otherwise the right.

For chained defaults:

let value = first ?? second ?? third ?? "fallback"

Force-unwrap !

The ! extracts the wrapped value or crashes:

let name: String? = "Alice"
let length = name!.count                           // 5

let nilName: String? = nil
let oops = nilName!.count                          // CRASH: unexpectedly found nil

The conventional discipline avoids !if let, guard let, ??, and ?. are conventionally safer. The ! is admitted only when:

  • The value is guaranteed to be non-nil by surrounding logic (e.g., immediately after checking).
  • The crash is the intended behaviour on absence (e.g., reading a constant that must be present).
  • Implicitly unwrapped optionals (treated below) admit substantial automatic unwrapping.

Implicitly unwrapped optionals (T!)

The T! syntax declares an optional that admits automatic unwrapping:

var name: String! = "Alice"

let length = name.count                            // OK; auto-unwrapped (no ?. or !)

name = nil
let oops = name.count                              // CRASH on auto-unwrap

The mechanism is conventional in Objective-C bridging (Swift treats certain Objective-C return types as T! — admit nil but typically not).

The conventional contemporary discipline avoids T! in pure Swift code; T? with explicit unwrapping is conventionally clearer.

Optional methods

The Optional type has a substantial method surface:

let name: String? = "Alice"

// Map: transform if non-nil:
let upperName = name.map { $0.uppercased() }       // "ALICE" (Optional<String>)

// FlatMap: chain optionals:
let length = name.flatMap { Int($0) }              // returns Int? (or nil if not parseable)

// Default value:
let display = name ?? "anonymous"

// Has value?
if name != nil { /* ... */ }
let hasValue = name != nil

// Unwrap or throw:
let unwrapped = try name ?? throw ParseError.empty

The map and flatMap admit substantial functional-style optional handling.

Pattern matching with optionals

Optionals admit switch patterns:

let n: Int? = 42

switch n {
case .none:
    print("nothing")
case .some(let value):
    print("got \(value)")
}

// Or with the optional pattern (using `?`):
switch n {
case nil:
    print("nothing")
case let value?:
    print("got \(value)")
}

// In if/guard case:
if case let value? = n {
    print("got \(value)")
}

if case let .some(value) = n {
    print("got \(value)")
}

Treated in Pattern matching.

Optional comparison

let a: Int? = 5
let b: Int? = nil

a == b                                             // false
a == 5                                             // true
a == .some(5)                                       // true
b == nil                                            // true
b == .none                                          // true

a > b                                              // ERROR: Optional<Int> isn't Comparable directly

The == is admitted; < and friends are not — comparisons require explicit unwrapping.

Common patterns

Default value

let port = config.port ?? 8080
let name = user?.name ?? "anonymous"

Early exit

func process(input: String?) -> Result {
    guard let input, !input.isEmpty else {
        return .failure(.emptyInput)
    }

    // input is unwrapped and non-empty
    return Result.success(transform(input))
}

Chained access

let city = user?.profile?.address?.city
let firstName = list?.first?.split(separator: " ").first

Optional binding with multiple values

guard let user, let permissions = user.permissions, !permissions.isEmpty else {
    throw AuthError.insufficientPermissions
}

// All three unwrapped and validated
grant(user, permissions)

if let with shorthand (Swift 5.7+)

if let user, let permissions = user.permissions {
    grant(user, permissions)
}

// Pre-5.7:
if let user = user, let permissions = user.permissions {
    grant(user, permissions)
}

Conditional initialisation

let name = optionalName ?? "default"

let user = User(name: name)

Optional with throw

extension Optional {
    func unwrap(or error: Error) throws -> Wrapped {
        guard let value = self else { throw error }
        return value
    }
}

let user = try optionalUser.unwrap(or: AuthError.notFound)

Map and flatMap

let raw: String? = "42"
let parsed: Int? = raw.flatMap { Int($0) }
let doubled: Int? = parsed.map { $0 * 2 }

// Equivalent without map:
var doubled2: Int?
if let raw, let n = Int(raw) {
    doubled2 = n * 2
}

Coalesce-or-throw

let value = try optionalValue ?? throw MyError.noValue

The try ?? throw pattern admits substantial concise error throwing.

if let for optional chain validation

if let url = URL(string: input),
   let response = try? Data(contentsOf: url),
   let data = String(data: response, encoding: .utf8) {
    print(data)
}

Computed property with optional

extension User {
    var displayName: String {
        nickname ?? "\(firstName) \(lastName)"
    }
}

Type-cast optional

let value: Any = "hello"

if let s = value as? String {
    print(s.uppercased())
}

Optional sequence access

let arr = [1, 2, 3]

let first = arr.first                              // Optional<Int>
let middle = arr.dropFirst().first                 // Optional<Int>
let last = arr.last

// Subscript (returns Optional only via dictionaries):
let dict = ["a": 1]
let value = dict["a"]                              // Optional<Int>
let missing = dict["nope"]                         // nil

Failable initialisers

A failable initialiser returns an optional:

struct Email {
    let value: String

    init?(_ raw: String) {
        guard raw.contains("@") else { return nil }
        self.value = raw
    }
}

let valid = Email("alice@example.com")             // Optional<Email>
let invalid = Email("not-an-email")                // nil

The init? is the conventional Swift pattern for “construction may fail”.

A note on Optional in error handling

The Optional and Result types are complementary:

  • Optional<T> — for “value may be absent” with no specific reason.
  • Result<T, E> — for “fallible operation” with an error.
  • throws — for exception-style error handling.
// Optional return:
func find(_ id: Int) -> User? { /* ... */ }

// Result return:
func fetch(_ id: Int) -> Result<User, FetchError> { /* ... */ }

// Throwing:
func decode(_ data: Data) throws -> User { /* ... */ }

Treated in Error handling.

A note on the conventional discipline

The contemporary Swift optionals advice:

  • Use T? for any value that may be absent — never use nil as a sentinel for non-optional types.
  • Use if let / guard let for unwrapping.
  • Use ?. for optional chaining.
  • Use ?? for default values.
  • Avoid ! (force-unwrap) — only when the value is guaranteed non-nil.
  • Avoid T! (implicitly unwrapped optionals) — T? is conventionally clearer.
  • Use the shorthand if let name (Swift 5.7+) for the same-name pattern.
  • Use init? for failable initialisers.
  • Use map and flatMap on optionals for substantial functional patterns.
  • Use guard let over if let when the value is required for the rest of the scope.

The combination — explicit nullability at the type level, the substantial unwrapping operators, the compile-time enforcement, the Optional enum’s substantial methods — is the substance of Swift’s nullability mechanism. The discipline eliminates substantial classes of nil-pointer bugs at compile time; the cost is some additional verbosity around values that may be absent.