Skip to content

Commit e32db08

Browse files
committed
[doc][stdlib] Expand the coding style section of the stdlib programmers manual
- Explain why we have such a short line length limit - Expand a bit on line breaking conventions - Add a section on how to present type definitions
1 parent dd40ff2 commit e32db08

File tree

1 file changed

+127
-1
lines changed

1 file changed

+127
-1
lines changed

Diff for: docs/StandardLibraryProgrammersManual.md

+127-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,136 @@ In this document, "stdlib" refers to the core standard library (`stdlib/public/c
1212

1313
### Formatting Conventions
1414

15-
The stdlib currently has a hard line length limit of 80 characters. To break long lines, please closely follow the indentation conventions you see in the existing codebase. (FIXME: Describe.)
15+
The Standard Library codebase has some rather strict formatting conventions. While these aren't currently automatically enforced, we still expect these conventions to be followed in every PR, including draft PRs. (PRs are first and foremost intended to be read/reviewed by *people familiar with the stdlib codebase*, and it's crucial that trivial formatting issues don't get in the way of understanding proposed changes.)
16+
17+
#### Line Breaking
18+
19+
The stdlib currently has a hard line length limit of 80 characters. This allows code to be easily read in environments that don't gracefully handle long lines, including (especially!) code reviews on GitHub.
1620

1721
We use two spaces as the unit of indentation. We don't use tabs.
1822

23+
To break long lines, please closely follow the indentation conventions you see in the existing codebase. (FIXME: Describe in detail.)
24+
25+
Our primary rule is that if we need to break a list (such as arguments, tuple or array/dictionary literals, generic type parameters, etc.), then we always put each item on its own line, indented by +1 unit, even if a series of items would fit on a single line together.
26+
27+
The rationale for this is that line breaks tend to put strong visual emphasis on the item that follows them, allowing subsequent items on the same line to be glanced over during review. For example, see how easy it is to accidentally miss `arg2` in the second example below.
28+
29+
```swift
30+
// BAD: (completely unreadable)
31+
@inlinable public func foobar<Result>(_ arg1: Result, arg2: Int, _ arg3: (Result, Element) throws -> Result) rethrows -> Result {
32+
...
33+
}
34+
35+
// BAD: (arg2 is easily missed)
36+
@inlinable
37+
public func foobar<Result>(
38+
_ arg1: Result, arg2: Int, // ☹️
39+
_ arg3: (Result, Element) throws -> Result
40+
) rethrows -> Result {
41+
42+
// GOOD:
43+
@inlinable
44+
public func foobar<Result>(
45+
_ arg1: Result,
46+
arg2: Int,
47+
_ arg3: (Result, Element) throws -> Result
48+
) rethrows -> Result {
49+
...
50+
}
51+
```
52+
53+
The rules typically don't require breaking lines that don't exceed the length limit; but if you find it helps understanding, feel free to do so anyway.
54+
55+
```swift
56+
// OK
57+
guard let foo = foo else { return false }
58+
59+
// Also OK
60+
guard let foo = foo else {
61+
return false
62+
}
63+
```
64+
65+
#### Presentation of Type Definitions
66+
67+
To ease reading/understanding type declarations, we prefer to define members in the following order:
68+
69+
1. Crucial type aliases and nested types, not exceeding a handful of lines in length
70+
2. Stored properties
71+
3. Initializers
72+
4. Any other instance members (methods, calculated properties)
73+
74+
Please keep all stored properties together in a single uninterrupted list, followed immediately by the type's most crucial initializer(s). Put these as close to the top of the type declaration as possible -- we don't want to force readers to scroll around to find these core definitions.
75+
76+
The main declaration ought to be kept as short as possible -- preferably it should consist of the type's stored properties and a handful of critical initializers, and nothing else.
77+
Everything else should go in standalone extensions, arranged by logical theme. For example, it's often better to define protocol conformances in dedicated extensions. Think about what order you present these -- put related conformances together, follow some didactic arc, etc. (E.g, conformance definitions for `Equatable`/`Hashable`/`Comparable` should be kept very close to each other, for easy referencing.)
78+
79+
It's okay for the core type declaration to forward reference large nested types or static members that are defined in subsequent extensions. It's often a good idea to define these in an extension immediately following the type declaration, but this is not a strict rule. The goal is to make things easy to understand -- if a type is small enough, it may be fine to put every member directly in the `struct`/`class` definition, while it may make sense to break the definition of a huge type into a number of source files.
80+
81+
```
82+
// BAD (a jumbled mess)
83+
struct Foo: RandomAccessCollection, Hashable {
84+
var count: Int { ... }
85+
86+
struct Iterator: IteratorProtocol { /* hundreds of lines */ }
87+
88+
class _Storage { /* even more lines */ }
89+
90+
static func _createStorage(_ foo: Int, _ bar: Double) -> _Storage { ... }
91+
92+
func hash(into hasher: inout Hasher) { ... }
93+
94+
func makeIterator() -> Iterator { ... }
95+
96+
/* more stuff */
97+
98+
init(foo: Int, bar: Double) {
99+
_storage = Self._createStorage(foo, bar)
100+
}
101+
102+
static func ==(left: Self, right: Self) -> Bool { ... }
103+
104+
var _storage: _Storage
105+
}
106+
107+
// GOOD
108+
struct Foo: RandomAccessCollection, Hashable {
109+
var _storage: _FooStorage
110+
111+
init(foo: Int, bar: Double) { ... }
112+
}
113+
114+
extension Foo {
115+
class _Storage { /* even more lines */ }
116+
117+
static func _createStorage(_ foo: Int, _ bar: Double) -> _Storage { ... }
118+
}
119+
120+
extension Foo: Equatable {
121+
static func ==(left: Self, right: Self) -> Bool { ... }
122+
}
123+
124+
extension Foo: Hashable {
125+
func hash(into hasher: inout Hasher) { ... }
126+
}
127+
128+
extension Foo: Sequence {
129+
struct Iterator: IteratorProtocol { /* hundreds of lines */ }
130+
131+
func makeIterator() -> Iterator { ... }
132+
...
133+
}
134+
135+
extension Foo: RandomAccessCollection {
136+
var count: Int { ... }
137+
...
138+
}
139+
140+
extension Foo {
141+
/* more stuff */
142+
}
143+
```
144+
19145
### Public APIs
20146

21147
#### Core Standard Library

0 commit comments

Comments
 (0)