Conventions and Special Rules
Casing conventions, complexity documentation, free function exceptions, overload safety, and tuple/closure naming.
Contents
- Casing Conventions
- Complexity Documentation
- Free Function Exceptions
- Overload Safety
- Tuple Member and Closure Parameter Naming
- Unconstrained Polymorphism
Casing Conventions
Types and protocols use UpperCamelCase. Everything else — functions, methods, properties, variables, constants, enum cases, argument labels — uses lowerCamelCase.
Acronym handling
Acronyms that appear as all-uppercase in common American English usage are uniformly upper- or lower-cased based on their position in the name.
// At the start of a lowerCamelCase name — all lowercase
var utf8Bytes: [UTF8.CodeUnit]
var htmlParser: HTMLParser
var urlSession: URLSession
// In the middle or end — follows the case of its position
var isRepresentableAsASCII = true
var userSMTPServer: SMTPServer
// Type names — acronym stays uppercase
struct HTTPRequest { ... }
class URLSessionTask { ... }
enum UTF8 { ... }Words that started as acronyms but are now common words follow standard casing:
// These are words, not acronyms
var radarDetector: RadarDetector // not "RADARDetector"
var laserPrinter: LaserPrinter // not "LASERPrinter"
var scubaGear: ScubaGear // not "SCUBAGear"Complexity Documentation
Callers assume computed properties are O(1). Document the complexity of any computed property or subscript that does more work.
/// The total number of nodes in the tree.
///
/// - Complexity: O(*n*), where *n* is the number of nodes.
var nodeCount: Int {
root.descendants.count
}/// The element at the given offset from the start.
///
/// - Complexity: O(*k*), where *k* is the offset.
subscript(offset: Int) -> Element {
var current = startIndex
for _ in 0..<offset { current = index(after: current) }
return self[current]
}Properties that are genuinely O(1) do not need a complexity note — the default assumption is correct.
// O(1) — no note needed
var count: Int { storage.count }
var isEmpty: Bool { count == 0 }
var first: Element? { storage.first }Free Function Exceptions
Prefer methods and properties to free functions. Use a free function only when one of these three conditions holds:
1. No obvious self
// GOOD — neither argument is more "self" than the other
min(x, y)
max(a, b)
zip(sequence1, sequence2)2. Unconstrained generic
// GOOD — works on anything
print(value)
debugPrint(value)3. Established domain notation
// GOOD — mathematical convention
sin(x)
cos(x)
abs(value)If none of these apply, make it a method.
// BAD — has an obvious self
calculateDistance(from: pointA, to: pointB)
// GOOD
pointA.distance(to: pointB)Overload Safety
Methods may share a base name when they operate in different type domains or when their meaning is clear from context.
// GOOD — different parameter types, same semantic operation
extension Collection {
func contains(_ element: Element) -> Bool
func contains(where predicate: (Element) -> Bool) -> Bool
}Avoid return-type-only overloads
Overloads that differ only in return type create ambiguity when the compiler cannot infer the expected type.
// BAD — ambiguous at call site
func transform() -> Int { ... }
func transform() -> String { ... }
let result = transform() // error: ambiguousSafe overloading patterns
// GOOD — different first argument labels disambiguate
func fetch(id: Int) -> User
func fetch(name: String) -> User
// GOOD — different argument types
func draw(_ rect: CGRect)
func draw(_ path: CGPath)Tuple Member and Closure Parameter Naming
Label tuple members and closure parameters in public API signatures. Positional access (.0, .1) is fragile and unreadable.
Tuple members
// GOOD — labeled
func position() -> (x: Double, y: Double)
func range() -> (lower: Int, upper: Int)
let pos = item.position()
pos.x // clear
pos.y // clear
// BAD — unlabeled
func position() -> (Double, Double)
let pos = item.position()
pos.0 // what is this?
pos.1 // what is this?Closure parameters
Name closure parameters where they appear in the API, especially for higher-order functions.
// GOOD
func filter(_ isIncluded: (Element) -> Bool) -> [Element]
func map<T>(_ transform: (Element) -> T) -> [T]
func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
// BAD
func filter(_ predicate: (Element) -> Bool) -> [Element] // acceptable but less descriptive
func sort(by compare: (Element, Element) -> Bool) // "compare" is vagueUnconstrained Polymorphism
Be careful with overloads involving Any, AnyObject, or unconstrained generics. They can silently match when a more specific overload was intended.
// DANGEROUS — both overloads match array literals
struct Container {
func append(_ element: Element)
func append(_ sequence: some Sequence<Element>)
}
values.append([2, 3, 4]) // which overload? ambiguous if Element is ArrayResolve with distinct argument labels:
// GOOD — unambiguous
struct Container {
func append(_ element: Element)
func append(contentsOf sequence: some Sequence<Element>)
}
values.append(singleValue)
values.append(contentsOf: [2, 3, 4]) // always clear