Swift.org (original) (raw)

API Design Guidelines

To facilitate use as a quick reference, the details of many guidelines can be expanded individually. Details are never hidden when this page is printed.

Table of Contents

Delivering a clear, consistent developer experience when writing Swift code is largely defined by the names and idioms that appear in APIs. These design guidelines explain how to make sure that your code feels like a part of the larger Swift ecosystem.

extension List {  
  public mutating func remove(at position: Index) -> Element  
}  
employees.remove(at: x)  

If we were to omit the word at from the method signature, it could imply to the reader that the method searches for and removes an element equal to x, rather than using x to indicate the position of the element to remove.

employees.remove(x) // unclear: are we removing x?  
public mutating func removeElement(_ member: Element) -> Element?  
allViews.removeElement(cancelButton)  

In this case, the word Element adds nothing salient at the call site. This API would be better:

public mutating func remove(_ member: Element) -> Element?  
allViews.remove(cancelButton) // clearer  

Occasionally, repeating type information is necessary to avoid ambiguity, but in general it is better to use a word that describes a parameter’s role rather than its type. See the next item for details.

var **string** = "Hello"  
protocol ViewController {  
  associatedtype **View**Type : View  
}  
class ProductionLine {  
  func restock(from **widgetFactory**: WidgetFactory)  
}  

Repurposing a type name in this way fails to optimize clarity and expressivity. Instead, strive to choose a name that expresses the entity’s role.

var **greeting** = "Hello"  
protocol ViewController {  
  associatedtype **ContentView** : View  
}  
class ProductionLine {  
  func restock(from **supplier**: WidgetFactory)  
}  

If an associated type is so tightly bound to its protocol constraint that the protocol name is the role, avoid collision by appendingProtocol to the protocol name:

protocol Sequence {  
  associatedtype Iterator : Iterator**Protocol**  
}  
protocol Iterator**Protocol** { ... }  
func add(_ observer: NSObject, for keyPath: String)  
grid.add(self, for: graphics) // vague  

To restore clarity, precede each weakly typed parameter with a noun describing its role:

func add**Observer**(_ observer: NSObject, for**KeyPath** path: String)  
grid.addObserver(self, forKeyPath: graphics) // clear  
x.insert(y, at: z)          <span class="commentary">“x, insert y at z”</span>  
x.subviews(havingColor: y)  <span class="commentary">“x's subviews having color y”</span>  
x.capitalizingNouns()       <span class="commentary">“x, capitalizing nouns”</span>  
x.insert(y, position: z)  
x.subviews(color: y)  
x.nounCapitalize()  

It is acceptable for fluency to degrade after the first argument or two when those arguments are not central to the call’s meaning:

AudioUnit.instantiate(  
  with: description,  
  **options: [.inProcess], completionHandler: stopProgressBar**)  
let foreground = **Color**(red: 32, green: 64, blue: 128)  
let newPart = **factory.makeWidget**(gears: 42, spindles: 14)  
let ref = **Link**(target: destination)  

In the following, the API author has tried to create grammatical continuity with the first argument.

let foreground = **Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)**  
let newPart = **factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)**  
let ref = **Link(to: destination)**  

In practice, this guideline along with those forargument labels means the first argument will have a label unless the call is performing avalue preserving type conversion.

let rgbForeground = RGBColor(cmykForeground)  

Term of Art

noun - a word or phrase that has a precise, specialized meaning within a particular field or profession.

var **utf8**Bytes: [**UTF8**.CodeUnit]  
var isRepresentableAs**ASCII** = true  
var user**SMTP**Server: Secure**SMTP**Server  

Other acronyms should be treated as ordinary words:

var **radar**Detector: **Radar**Scanner  
var enjoys**Scuba**Diving = true  
extension Shape {  
  /// Returns `true` if `other` is within the area of `self`;  
  /// otherwise, `false`.  
  func **contains**(_ other: **Point**) -> Bool { ... }  
  /// Returns `true` if `other` is entirely within the area of `self`;  
  /// otherwise, `false`.  
  func **contains**(_ other: **Shape**) -> Bool { ... }  
  /// Returns `true` if `other` is within the area of `self`;  
  /// otherwise, `false`.  
  func **contains**(_ other: **LineSegment**) -> Bool { ... }  
}  

And since geometric types and collections are separate domains, this is also fine in the same program:

extension Collection where Element : Equatable {  
  /// Returns `true` if `self` contains an element equal to  
  /// `sought`; otherwise, `false`.  
  func **contains**(_ sought: Element) -> Bool { ... }  
}  

However, these index methods have different semantics, and should have been named differently:

extension Database {  
  /// Rebuilds the database's search index  
  func **index**() { ... }  
  /// Returns the `n`th row in the given table.  
  func **index**(_ n: Int, inTable: TableID) -> TableRow { ... }  
}  

Lastly, avoid “overloading on return type” because it causes ambiguities in the presence of type inference.

extension Box {  
  /// Returns the `Int` stored in `self`, if any, and  
  /// `nil` otherwise.  
  func **value**() -> Int? { ... }  
  /// Returns the `String` stored in `self`, if any, and  
  /// `nil` otherwise.  
  func **value**() -> String? { ... }  
}  
func move(from **start**: Point, to **end**: Point)
/// Return an `Array` containing the elements of `self`  
/// that satisfy `**predicate**`.  
func filter(_ **predicate**: (Element) -> Bool) -> [Generator.Element]  
/// Replace the given `**subRange**` of elements with `**newElements**`.  
mutating func replaceRange(_ **subRange**: Range<Index>, with **newElements**: [E])  

These, however, make the documentation awkward and ungrammatical:

/// Return an `Array` containing the elements of `self`  
/// that satisfy `**includedInResult**`.  
func filter(_ **includedInResult**: (Element) -> Bool) -> [Generator.Element]  
/// Replace the **range of elements indicated by `r`** with  
/// the contents of `**with**`.  
mutating func replaceRange(_ **r**: Range<Index>, **with**: [E])  
let order = lastName.compare(  
  royalFamilyName**, options: [], range: nil, locale: nil**)  

can become the much simpler:

let order = lastName.**compare(royalFamilyName)**  

Default arguments are generally preferable to the use of method families, because they impose a lower cognitive burden on anyone trying to understand the API.

extension String {  
  /// *...description...*  
  public func compare(  
     _ other: String, options: CompareOptions **= []**,  
     range: Range<Index>? **= nil**, locale: Locale? **= nil**  
  ) -> Ordering  
}  

The above may not be simple, but it is much simpler than:

extension String {  
  /// *...description 1...*  
  public func **compare**(_ other: String) -> Ordering  
  /// *...description 2...*  
  public func **compare**(_ other: String, options: CompareOptions) -> Ordering  
  /// *...description 3...*  
  public func **compare**(  
     _ other: String, options: CompareOptions, range: Range<Index>) -> Ordering  
  /// *...description 4...*  
  public func **compare**(  
     _ other: String, options: StringCompareOptions,  
     range: Range<Index>, locale: Locale) -> Ordering  
}  

Every member of a method family needs to be separately documented and understood by users. To decide among them, a user needs to understand all of them, and occasional surprising relationships—for example, foo(bar: nil) and foo() aren’t always synonyms—make this a tedious process of ferreting out minor differences in mostly identical documentation. Using a single method with defaults provides a vastly superior programmer experience.

func move(**from** start: Point, **to** end: Point)
x.move(**from:** x, **to:** y)
extension String {  
  // Convert `x` into its textual representation in the given radix  
  init(**_** x: BigInt, radix: Int = 10)   <span class="commentary">← Note the initial underscore</span>  
}  
text = "The value is: "  
text += **String(veryLargeNumber)**  
text += " and in hexadecimal, it's"  
text += **String(veryLargeNumber, radix: 16)**  

In “narrowing” type conversions, though, a label that describes the narrowing is recommended.

extension UInt32 {  
  /// Creates an instance having the specified `value`.  
  init(**_** value: Int16)            <span class="commentary">← Widening, so no label</span>  
  /// Creates an instance having the lowest 32 bits of `source`.  
  init(**truncating** source: UInt64)  
  /// Creates an instance having the nearest representable  
  /// approximation of `valueToApproximate`.  
  init(**saturating** valueToApproximate: UInt64)  
}  

A value preserving type conversion is amonomorphism, i.e. every difference in the value of the source results in a difference in the value of the result. For example, conversion from Int8 to Int64 is value preserving because every distinct Int8 value is converted to a distinct Int64 value. Conversion in the other direction, however, cannot be value preserving: Int64 has more possible values than can be represented in an Int8.

Note: the ability to retrieve the original value has no bearing on whether a conversion is value preserving.

a.move(**toX:** b, **y:** c)  
a.fade(**fromRed:** b, **green:** c, **blue:** d)  

In such cases, begin the argument label after the preposition, to keep the abstraction clear.

a.moveTo(**x:** b, **y:** c)  
a.fadeFrom(**red:** b, **green:** c, **blue:** d)  
view.dismiss(**animated:** false)  
let text = words.split(**maxSplits:** 12)  
let studentsByName = students.sorted(**isOrderedBefore:** Student.namePrecedes)  

Note that it’s important that the phrase convey the correct meaning. The following would be grammatical but would express the wrong thing.

view.dismiss(false)   <span class="commentary">Don't dismiss? Dismiss a Bool?</span>  
words.split(12)       <span class="commentary">Split the number 12?</span>  

Note also that arguments with default values can be omitted, and in that case do not form part of a grammatical phrase, so they should always have labels.

/// Ensure that we hold uniquely-referenced storage for at least  
/// `requestedCapacity` elements.  
///  
/// If more storage is needed, `allocate` is called with  
/// **`byteCount`** equal to the number of maximally-aligned  
/// bytes to allocate.  
///  
/// - Returns:  
///   - **reallocated**: `true` if a new block of memory  
///     was allocated; otherwise, `false`.  
///   - **capacityChanged**: `true` if `capacity` was updated;  
///     otherwise, `false`.  
mutating func ensureUniqueStorage(  
  minimumCapacity requestedCapacity: Int,  
  allocate: (_ **byteCount**: Int) -> UnsafePointer&lt;Void&gt;  
) -> (**reallocated:** Bool, **capacityChanged:** Bool)  

Names used for closure parameters should be chosen likeparameter names for top-level functions. Labels for closure arguments that appear at the call site are not supported.

struct Array<Element> {  
  /// Inserts `newElement` at `self.endIndex`.  
  public mutating func append(_ newElement: Element)  
  /// Inserts the contents of `newElements`, in order, at  
  /// `self.endIndex`.  
  public mutating func append<S: SequenceType>(_ newElements: S)  
    where S.Generator.Element == Element  
}  

These methods form a semantic family, and the argument types appear at first to be sharply distinct. However, when Elementis Any, a single element can have the same type as a sequence of elements.

var values: [Any] = [1, "a"]  
values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?  

To eliminate the ambiguity, name the second overload more explicitly.

struct Array {  
  /// Inserts `newElement` at `self.endIndex`.  
  public mutating func append(_ newElement: Element)  
  /// Inserts the contents of `newElements`, in order, at  
  /// `self.endIndex`.  
  public mutating func append<S: SequenceType>(**contentsOf** newElements: S)  
    where S.Generator.Element == Element  
}  

Notice how the new name better matches the documentation comment. In this case, the act of writing the documentation comment actually brought the issue to the API author’s attention.