Beginner

Variables and Types in Swift

Learn about variables, constants, type inference, and optionals in Swift with practical Docker-ready examples

Swift’s type system is one of its defining features: static and strong, yet flexible enough that you rarely need to write types explicitly. The compiler infers types from context, catches mismatches at compile time, and introduces optionals to handle the absence of a value safely — eliminating an entire class of null pointer crashes.

As a multi-paradigm language, Swift treats both value types (structs, enums) and reference types (classes) as first-class citizens. This tutorial focuses on the foundational building blocks: declaring variables and constants, working with Swift’s core types, converting between them, and understanding optionals.

Variables and Constants

Swift draws a sharp distinction between mutable variables (var) and immutable constants (let). Prefer let by default; the compiler will tell you when you actually need var.

Create a file named variables.swift:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Constants are declared with 'let' — value cannot change
let language = "Swift"
let version: Double = 6.0
let isOpenSource: Bool = true

// Variables are declared with 'var' — value can be reassigned
var year: Int = 2014
var downloads = 1_000_000  // underscores improve readability in numbers

print("Language: \(language)")
print("Version: \(version)")
print("Open source: \(isOpenSource)")
print("First released: \(year)")
print("Downloads: \(downloads)")

// Reassigning a var is fine
year = 2024
downloads += 500_000
print("Current year: \(year)")
print("Total downloads: \(downloads)")

// Type annotations are optional when the type can be inferred
let radius = 5.0        // inferred as Double
let diameter = radius * 2
print("Diameter: \(diameter)")

Swift’s type inference is pervasive: let language = "Swift" is equivalent to let language: String = "Swift". You only need explicit annotations when the inferred type isn’t what you want (e.g., let x: Float = 3.14 instead of the default Double).

Core Types and Type Safety

Swift’s type system is strict: you cannot mix types without explicit conversion. This prevents subtle bugs that plague loosely-typed languages.

Create a file named variables_types.swift:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Integer types — Int is the default (platform-sized, 64-bit on modern hardware)
let population: Int = 8_100_000_000
let byte: UInt8 = 255
let small: Int8 = -128

// Floating-point types
let pi: Double = 3.14159265358979  // 64-bit, default for floating point
let gravity: Float = 9.81          // 32-bit, when precision can be sacrificed

// Swift requires explicit conversion between numeric types
let widthInt: Int = 1920
let scale: Double = 1.5
let scaledWidth = Double(widthInt) * scale  // explicit Int → Double conversion
print("Scaled width: \(scaledWidth)")

// String is a full-featured value type, not a class
let greeting = "Hello"
let punctuation: Character = "!"
let combined = greeting + ", Swift" + String(punctuation)
print(combined)

// String interpolation works with any type that has a description
let items = 7
let price = 4.99
print("Total: \(Double(items) * price)")

// Bool
let debug = false
let verbose = true
print("Debug mode: \(debug), Verbose: \(verbose)")

// Type checking at runtime
let value: Any = 42
if value is Int {
    print("value is an Int")
}

Notice that Swift will not implicitly widen Int to DoubleDouble(widthInt) must be written explicitly. This verbosity is intentional: it makes type conversions visible and prevents accidental precision loss.

Optionals: Safe Nil Handling

Swift’s answer to null pointer exceptions is the optional type. An optional String? is either a String or nothing at all — and the compiler forces you to handle both cases before using the value.

Create a file named variables_optionals.swift:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Optional types: a value may or may not exist
var username: String? = nil
var score: Int? = nil

print("Username before login: \(username ?? "not set")")

// Simulate a user logging in
username = "swiftcoder"
score = 1500

// Optional binding: safely unwrap with 'if let'
if let name = username {
    print("Welcome back, \(name)!")
}

// 'guard let' for early exit — preferred when nil means the function can't proceed
func greetUser(_ user: String?) {
    guard let name = user else {
        print("No user provided")
        return
    }
    print("Hello, \(name)")
}

greetUser(username)
greetUser(nil)

// Nil coalescing operator: provide a default when nil
let displayScore = score ?? 0
print("Score: \(displayScore)")

// Optional chaining: safely access properties/methods on optionals
let nameLength = username?.count
print("Username length: \(nameLength ?? 0)")

// Multiple optionals with 'if let' (Swift 5.9+ syntax)
if let name = username, let points = score {
    print("\(name) has \(points) points")
}

// Implicitly unwrapped optional: use only when you guarantee a value exists
// (common in iOS where outlets are set before use)
var title: String! = "Swift Tutorial"
print("Title: \(title!)")

Optionals appear throughout Swift APIs — any value that might be absent is an optional. The ?? nil coalescing operator and ?. optional chaining operators let you work with optionals concisely without force-unwrapping (!), which should be avoided unless you are certain the optional contains a value.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Pull the official Swift image
docker pull swift:6.0

# Run the basic variables example
docker run --rm -v $(pwd):/app -w /app swift:6.0 swift variables.swift

# Run the types example
docker run --rm -v $(pwd):/app -w /app swift:6.0 swift variables_types.swift

# Run the optionals example
docker run --rm -v $(pwd):/app -w /app swift:6.0 swift variables_optionals.swift

Expected Output

Running variables.swift:

Language: Swift
Version: 6.0
Open source: true
First released: 2014
Downloads: 1000000
Current year: 2024
Total downloads: 1500000
Diameter: 10.0

Running variables_types.swift:

Scaled width: 2880.0
Hello, Swift!
Total: 34.93
Debug mode: false, Verbose: true
value is an Int

Running variables_optionals.swift:

Username before login: not set
Welcome back, swiftcoder!
Hello, swiftcoder
No user provided
Score: 1500
Username length: 10
swiftcoder has 1500 points
Title: Swift Tutorial

Key Concepts

  • let vs var: Use let for constants (the default); use var only when the value needs to change. The compiler enforces this distinction.
  • Type inference: Swift deduces types from the assigned value — explicit annotations are optional but can be useful for clarity or to override the default (e.g., Float instead of Double).
  • No implicit conversions: Swift never silently widens or narrows numeric types. You must write Double(myInt) explicitly, which keeps type boundaries visible in code.
  • String is a value type: Unlike Java or C#, Swift String is a struct that copies on assignment, not a heap-allocated object — which makes its behavior predictable and thread-safe.
  • Optionals (T?): The type system itself encodes the presence or absence of a value. A String? and a String are distinct types — you cannot use one where the other is expected.
  • Optional binding (if let, guard let): The idiomatic way to unwrap optionals safely. Avoid force unwrapping (!) in production code.
  • Nil coalescing (??): Provides a default value inline when an optional is nil, reducing boilerplate compared to explicit if/else checks.
  • Any and AnyObject: Swift provides escape hatches for dynamic typing, but strongly prefers generics and protocols for type-safe polymorphism.

Running Today

All examples can be run using Docker:

docker pull swift:6.0
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining