# Swift fundamentals

Source: https://codewiki.com/swift/fundamentals/

> - **what**: Swift is statically typed: every value has a type, and the compiler checks how values move through declarations, expressions, functions, and collections.
> - **trap**: Inference doesn't perform implicit numeric conversions or make unsafe input valid; array subscripts and forced optional unwrapping can still trap at runtime.
> - **fix**: Prefer `let`, convert at explicit boundaries, validate optional results with `if let` or `guard let`, and choose a collection whose ordering and uniqueness match the problem.

## What it is and why it exists

Swift's fundamentals are the rules that connect names, values, types, and control flow.
A declaration binds a name with `let` or `var`; its type determines which operations are legal.
Functions package those operations, while arrays, sets, and dictionaries organize groups of typed values.

Swift is statically and strongly typed.
The compiler knows the type of every expression before the program runs and rejects mismatched operations, such as adding an `Int` directly to a `Double`.
This catches a large class of mistakes near their source, but it doesn't prove that a collection index is in range or that an optional contains a value.

Most declarations use type inference.
In `let retryLimit = 3`, the integer literal and its context let the compiler infer `Int`; the declaration still has one fixed type.
Write an annotation such as `let timeout: Double = 3` when the intended type isn't obvious from the initializer or when an API contract should be visible.

Use `let` for a binding that won't be assigned a different value and `var` when reassignment is part of the algorithm.
This distinction makes mutation visible during review.
It describes the binding, however, not deep immutability of every object reachable through a class reference.

You meet these rules in every Swift program, from a command-line script to an app.
Input arrives as text or optional data, code converts and validates it, control flow chooses a path, and typed collections carry the result to the next layer.

## How it works

A Swift declaration has a name, a type, and a value before it is read.
An initializer can supply both the value and enough context for inference.
A declaration without an initializer needs an explicit type and must be initialized on every path before use; assigning a `let` once later is legal when the compiler can prove it happens exactly once.

The common scalar types are `Int`, `Double`, `Bool`, `String`, and `Character`.
Integer types model whole numbers within fixed bounds, floating-point types approximate real numbers, and `Bool` is accepted directly by conditions.
Swift doesn't treat `0`, an empty string, or `nil` as false.

Numeric types do not mix implicitly.
Construct the destination type at the point where units and precision have been considered, such as `Double(itemCount) * unitPrice`.
Parsing has a different failure mode: `Int("12")` returns `Int?` because arbitrary text may not contain a valid integer.

An optional value, written `Wrapped?`, contains either a wrapped value or `nil`.
Optional binding with `if let` opens one branch only when a value exists.
`guard let` is useful when the rest of a function requires the value, because its `else` branch must leave the current scope.

Swift's main collection types express different contracts:

- `Array` keeps an ordered sequence and permits duplicates.
- `Set` keeps unique hashable elements and has no application-level ordering contract.
- `Dictionary<Key, Value>` associates unique hashable keys with values; a lookup returns an optional because a key may be absent.

All elements in one collection have the declared element type.
Literal context often supplies it, as in `let ports: [Int] = [80, 443]`.
An empty literal needs context: use `[String]()` for an empty array, `Set()` for an empty set, and `[String: Int]()` or `[:]` with an annotation for an empty dictionary.

Conditions and loops compose the operations.
`if` and `switch` choose among paths, `for`-`in` visits a sequence, and `while` repeats while a Boolean condition remains true.
A `switch` must be exhaustive, so every possible input reaches a branch or a deliberate `default`.

Functions give a typed name to a calculation.
Parameters and return values are part of the function's type, and parameters are constants unless declared `inout`.
Swift uses an argument label by default; writing `_` before a parameter name omits the label at the call site.

The compiler checks this model from the inside out.
Literals and surrounding context establish types, operators constrain their operands, and a function call must match its parameter and result types.
Runtime checks remain at data-dependent boundaries such as parsing, integer arithmetic, collection indexing, and optional unwrapping.

## Examples

### Bind values and make a decision

This checkout keeps money in integer cents, avoiding an accidental mix of integer quantities and floating-point prices.
The compiler infers the local types.
Only `subtotal` uses `var` because the discount branch reassigns it.

<!-- quick -->

```swift
// file: checkout.swift
// # not executed here: Swift toolchain is not installed.
let unitPrice = 125
let quantity = 3
var subtotal = unitPrice * quantity
let hasMemberDiscount = true

if hasMemberDiscount {
    subtotal -= 25
}

let shipping = subtotal >= 300 ? 0 : 40
let total = subtotal + shipping

print("items: \(quantity)")
print("subtotal: \(subtotal)")
print("shipping: \(shipping)")
print("total: \(total)")
```

```text
Not executed here: Swift toolchain is not installed.
```

<!-- /quick -->

The ternary expression gives `shipping` one value from two branches, so that binding can remain a constant.
All arithmetic stays in `Int`, and the names preserve the unit decision.
In production money code, use an integer minor unit or a domain type rather than an unqualified `Double`.

### Parse boundary data without forcing it

Dictionary lookup and integer parsing both return optionals.
`parseSeats(_:)` uses one `guard` to require the field, parse it, and enforce the accepted range before returning a plain `Int`.

```swift
// file: seat_requests.swift
// # not executed here: Swift toolchain is not installed.
func parseSeats(_ fields: [String: String]) -> Int? {
    guard
        let rawSeats = fields["seats"],
        let seats = Int(rawSeats),
        (1...8).contains(seats)
    else {
        return nil
    }

    return seats
}

let requests = [
    ["name": "Mina", "seats": "4"],
    ["name": "Noah", "seats": "many"],
    ["name": "Iris"],
]

for request in requests {
    let name = request["name", default: "anonymous"]
    if let seats = parseSeats(request) {
        print("\(name): \(seats)")
    } else {
        print("\(name): invalid")
    }
}
```

```text
Not executed here: Swift toolchain is not installed.
```

The caller can't distinguish a missing field from malformed or out-of-range text because this small function deliberately maps all three cases to `nil`.
Use a result or throwing function when the caller needs different recovery or diagnostics.
Invalid input therefore never reaches the booking calculation as a numeric seat count.

### Choose collections by their contract

An array preserves the incoming tag order and duplicates.
A set removes duplicates, then `sorted()` creates a predictable array for display.
The dictionary stores a derived value for each unique tag.

```swift
// file: tag_summary.swift
// # not executed here: Swift toolchain is not installed.
let rawTags = ["swift", "ios", "swift", "testing", "ios"]
let uniqueTags = Set(rawTags)
let displayTags = uniqueTags.sorted()

let lengths = Dictionary(
    uniqueKeysWithValues: displayTags.map { tag in
        (tag, tag.count)
    }
)

for tag in displayTags {
    print("\(tag): \(lengths[tag, default: 0])")
}

let longTags = displayTags.filter { $0.count >= 5 }
print("long: \(longTags.joined(separator: ", "))")
```

```text
Not executed here: Swift toolchain is not installed.
```

The explicit sort is part of the output contract; iteration over `uniqueTags` would not provide a meaningful order to callers.
`Dictionary(uniqueKeysWithValues:)` requires every key to be unique.
That precondition is satisfied here because the pairs come from a set, but generated code often applies the initializer to unchecked input and traps on duplicates.

### Observe value semantics

Swift arrays and dictionaries have value semantics.
A function can copy an input collection into a local variable, mutate that local value, and return it without changing the caller's original value.

```swift
// file: collection_copies.swift
// # not executed here: Swift toolchain is not installed.
func addingPriority(to jobs: [String]) -> [String] {
    var result = jobs
    result.insert("incident", at: 0)
    return result
}

let queuedJobs = ["backup", "report"]
let priorityJobs = addingPriority(to: queuedJobs)

print("queued:", queuedJobs.joined(separator: ", "))
print("priority:", priorityJobs.joined(separator: ", "))

var profile = ["theme": "light"]
let savedProfile = profile
profile["theme"] = "dark"

print("current:", profile["theme", default: "missing"])
print("saved:", savedProfile["theme", default: "missing"])
```

```text
Not executed here: Swift toolchain is not installed.
```

`queuedJobs` stays unchanged even though `result` started with the same elements.
The same rule separates `profile` and `savedProfile` after the mutation.
Swift's standard collections may share internal storage until a write, but that optimization does not change their observable value behavior.

## Pitfalls

> **Pitfall:** Treating inferred declarations as dynamically typed leads to assignments that cannot compile.
After `var count = 0`, the binding is an `Int`; assigning `"zero"` later does not change its type.

**Fix:** choose one stable type for each declaration.
Add an explicit annotation when literal inference could hide the intended representation, and convert incoming values at a named boundary rather than scattering casts through the algorithm.

> **Pitfall:** A numeric conversion is not permission to ignore range, precision, or units.
`Int` and `Double` don't combine implicitly, and converting a floating-point value to `Int` deliberately discards its fractional part when the value is representable.

**Fix:** decide which type owns the calculation and convert once after validating the source.
Use `Int(exactly:)` when a conversion must preserve the value exactly, and keep unit names such as `priceInCents` visible in identifiers.

> **Pitfall:** `array[index]` does not return an optional.
A negative or out-of-range index traps, just as `optional!` traps when the value is `nil`.

**Fix:** prefer iteration, `first`, or `last` when they express the task.
When an external integer really selects an element, check `array.indices.contains(index)` before subscripting; unwrap parsing and dictionary lookup before the bounds check.

> **Pitfall:** Set and dictionary iteration order is not a presentation contract.
Output that appears stable in one run can be coupled accidentally to construction or implementation details.

**Fix:** sort keys or values explicitly whenever order is observable, including snapshots, command output, encoded data with ordering requirements, and tests.
Keep the unordered collection when uniqueness or lookup is the real need.

> **Pitfall:** A Swift `String` is not an array of UTF-16 code units, bytes, or integer-indexed characters.
User-perceived characters can contain several Unicode scalars, so generated code such as `text[0]` doesn't compile.

**Fix:** iterate over `Character` values or use `String.Index` operations.
Choose the correct lower-level view, such as `utf8`, only when a protocol requires code units, and don't confuse byte count with displayed character count.

<!-- deep -->

## Value semantics are observable

Swift's standard `String`, `Array`, `Set`, and `Dictionary` types are values.
Assignment, argument passing, and returns produce logically independent values, so mutating one variable does not mutate another value copied from it.
User-defined structures and enumerations follow the same model, though a value can still contain a reference to a shared class instance.

This is an interface guarantee, not a claim that every assignment immediately duplicates every byte.
Think in terms of observable mutation: if `let snapshot = current` captures a collection value, later structural changes to `current` do not change `snapshot`.
If both collections contain the same mutable class instances, mutations inside those instances remain shared because the elements themselves are references.

Function signatures use this model by default.
A collection parameter is a value passed into the function, and the parameter name cannot be reassigned or mutated directly.
Copy it into a local `var` and return the new value, or use `inout` when the API intentionally mutates the caller's storage and that ownership is clear at the call site.

## Copy-on-write is an optimization

Standard library value types can use copy-on-write to share storage while no mutation needs a unique buffer.
On mutation, the implementation preserves value semantics by making storage unique when necessary.
The fourth example therefore behaves as if the collections were independent even if they shared backing storage before the write.

Do not use copy-on-write as a synchronization story.
Two tasks mutating the same variable still create shared mutable state, and implementation details about buffer sharing are not an API guarantee.
Measure a real workload before making performance claims or replacing a clear value with manual reference storage.

Nested values require attention to their elements.
Copying an array of structures gives independent element values after mutation, while copying an array of class references copies the references.
Review the full object graph before claiming that a snapshot is isolated.

## String indices follow characters

A Swift `Character` represents an extended grapheme cluster, which may contain one or many Unicode scalars.
That model lets iteration follow user-perceived characters more closely than byte iteration.
It also means there is no constant mapping from an arbitrary integer offset to a character boundary.

`String.Index` records a valid position in a particular string view.
Obtain indices from the string itself with operations such as `startIndex`, `endIndex`, `index(after:)`, or `index(_:offsetBy:)`; validate reused or external positions before subscripting.
Traversing to a distant character may require work proportional to the distance, so repeated offset-based indexing is the wrong shape for a full scan.

Choose a view according to the boundary you are implementing.
Use `Character` iteration for text-facing behavior, `unicodeScalars` when scalar identity matters, and `utf8` for byte-oriented formats.
Normalization, locale-sensitive comparison, and display width are separate concerns; `count` alone does not answer all three.

## Integer arithmetic has boundaries

Swift's fixed-width integer types have minimum and maximum values.
Ordinary arithmetic that exceeds those bounds traps instead of silently wrapping in a release build.
Choose a type that covers the domain, validate untrusted operands before calculation, and test the largest accepted values together rather than one at a time.

The overflow operators `&+`, `&-`, and `&*` wrap deliberately.
They belong in algorithms whose contract is modular arithmetic, not as a patch for an unexplained overflow.
Generated code sometimes substitutes them to stop a crash and quietly changes an invalid transaction into an apparently valid total.

Ranges also encode boundary choices.
`1...8` includes both endpoints, while `1..<8` excludes the upper endpoint.
Empty or reversed ranges require care, and collection slicing should derive boundaries from that collection instead of assuming zero-based integer positions work for every collection type.

## Optional defaults define policy

The nil-coalescing operator evaluates its right side only when the optional on the left is `nil`.
This is concise when absence genuinely means a domain default, such as a missing display nickname becoming `"Anonymous"`.
It is misleading when `nil` covers malformed or rejected input and the fallback looks like a successful value.

Optional chaining stops at the first absent link and makes the overall result optional.
That behavior is useful for a read whose absence can flow outward.
A chain is less useful when each missing link needs a different message, because the final `nil` no longer says which assumption failed.

Keep the reason for absence until the layer that owns the fallback decision.
A small parser may intentionally return `nil` for every invalid form; a request boundary may need an error type that separates a missing field, bad syntax, and a disallowed value.
The type should preserve as much information as its caller needs, no more and no less.

## Scope and files shape names

Braces create local scopes for functions, loops, and branches.
A declaration can shadow an outer name, but repeated generic names such as `value` or `result` make logs and reviews difficult once scopes nest.
Prefer names that retain the domain and unit, especially at conversion boundaries.

Swift permits declarations at file scope, and a command-line source file can execute top-level statements.
Larger programs normally keep boundary work near an entry point and move calculations into functions with explicit parameter and result types.
This makes malformed input tests independent from process startup and console output.

Access control is another part of the declaration contract.
A helper that only supports one file can be `private` or `fileprivate`, while module-facing declarations may use `internal`, Swift's default.
Do not expose a declaration merely because generated code put every helper at top level.

Review a basic Swift file in this order:

1.
Identify every external string, optional field, numeric unit, and collection-order assumption.
2.
Check that each binding has the intended static type and the narrowest necessary mutability.
3.
Follow every invalid input to an explicit rejection, fallback, or propagated failure.
4.
Inspect observable output for deterministic ordering and correct Unicode boundaries.

The compiler proves type compatibility, definite initialization, and exhaustive switches.
The author still decides which inputs are trusted, which defaults are truthful, and which mutations belong to shared state.
With those decisions visible in source, compiler diagnostics cover type mistakes and tests can cover the remaining data-dependent paths.

<!-- /deep -->

[Checkpoint: swift/fundamentals](https://codewiki.com/swift/fundamentals/#checkpoint)

## Further reading

- [The Swift Programming Language: The Basics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/)
- [The Swift Programming Language: Collection Types](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/collectiontypes/)
- [The Swift Programming Language: Strings and Characters](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/stringsandcharacters/)
- [The Swift Programming Language: Functions](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/functions/)
