This was discussed previously either to always require self SE-0009 (rejected), or have an optional compiler warning (rejected).
Let's discuss whether and when this should be a recommended practice.
Required vs Optional
There is only one case where you are required to write explicit self – to refer to a member shadowed by another variable.
Other cases are optional (for self in closures see below), and are open to discussion whether it should be a recommended practice.
Criteria
To decide whether or not to use explicit self when it is optional.
The arguments are:
- it reduces errors
- it improves code readability
The errors reported from not using explicit self boil down to:
- confused shadowing
- unexpected strong reference to
self
1. (Required) Instance member is shadowed
1.a Argument to initializer
class Style1a {
init(bar: Int) {
self.bar = bar
}
let bar: Int
}
1.b Local variable
class Style1b {
var bar: Int = 0
func foo() {
let bar = 42
self.bar = bar
}
}
2. Confused shadowing
2.1 Readability of shadowed code
class Style21a {
func foo() {
if var bar = bar {
bar = bar + 1
}
bar = 42
}
var bar: Int?
}
class Style21b {
func foo() {
if let bar = self.bar {
self.bar = bar + 1
}
self.bar = 42
}
var bar: Int?
}
Pros of explicit self:
- Easier to reason, you can always tell when it refers to instance member
Cons:
- When reading,
self.adds noise
Alternatives:
- Review the code in full, follow logic flow
IDEReview with "highlight all appearances of the selected symbol in the enclosing code scope"IDERely on syntax highlighting difference for instance and non-instance variables
2.2 Adding shadowing
I just copy-pasted a working code block
Starting with this code:
class Style22 {
func foo() {
bar += 1
}
var bar: Int = 0
}
And then at some point introducing shadowing on line 3:
class Style22 {
func foo() {
var bar = 42
bar += 1
}
var bar: Int = 0
}
This happens when pasting code, or just by adding local variable.
Pros of explicit self:
- There will be no shadowing
- Compiler might warn you of unused local variable, or assigning to read-only variable, but this depends on code pasted
- When reading, you can always tell it refers to instance member
Cons:
- You might never add shadowing,
selfis unneeded - When reading,
self.adds noise
Alternatives:
- Cover the code with tests to ensure proper behavior
- Review the code in full, follow logic flow
IDEReview with "highlight all appearances of the selected symbol in the enclosing code scope"IDERely on syntax highlighting difference for instance and non-instance variablesIDEcould warn you that pasted code introduces shadowing
2.3 Removing shadowing
I thought it was a local variable
Starting with this code:
class Style23 {
func foo() {
var bar = 42
bar += 1
}
var bar: Int = 0
}
And then at some point removing line 3:
class Style23 {
func foo() {
bar += 1
}
var bar: Int = 0
}
In a more complex code you might believe you still have a local variable.
Pros of explicit self:
- If you miss explicit
self, linter will warn you, forcing a review
Cons:
- You have to update more code with
self, if this was intentional
Alternatives:
- Cover the code with tests to ensure proper behavior
- Review the code in full, follow logic flow
IDEReview with "highlight all appearances of the selected symbol in the enclosing code scope"IDERely on syntax highlighting difference for instance and non-instance variablesIDEcould warn you of removed shadowing
3. Unexpected strong reference to self
Note that confused shadowing could lead to unexpected strong reference, but we need to reason about them separately.
Exercise 1
Can you tell the line that introduces a reference cycle?
let s = Style30()
s.some = s.bar
s.some = s.nada
s.some = s.niente
What if I move this code inside a method? (bar, nada, and niente are still members of this class)
class Style30 {
var some: Any?
func foo() {
some = bar
some = nada
some = niente
}
And if I add self?
class Style30 {
var some: Any?
func foo() {
self.some = self.bar
self.some = self.nada
self.some = self.niente
}
Does self reveal a reference cycle?
Here is the rest of the class:
class Style30 {
var some: Any?
func bar() {}
let nada: Int = 42
let niente = Inner()
class Inner {}
}
Writing self does not reveal captured reference. To reason about references, you need to see the types involved, and have an implicit knowledge that method reference captures self. Nothing conveys this in language syntax – this is an unfortunate design deficiency.
Exercise 2
Can you tell the line that introduces a reference cycle?
class Style30b {
var some: Any?
func foo() {
self.some = {
self.bar()
self.nada
self.niente
self.niente.x
}
}
func bar() {}
let nada: Int = 42
let niente = Inner()
class Inner { let x = 11 }
}
Any reference to instance member inside a closure needs that instance captured. So all the lines 6-9 need self captured in that closure, which introduces reference cycle when that closure stored back on the instance.
3.1 Method reference
class Style31a {
init() {
some = bar
}
var some: Any?
func bar() {}
}
class Style31b {
init() {
self.some = self.bar
}
var some: Any?
func bar() {}
}
Line 3 introduces reference cycle.
Pros of explicit self:
- If you miss
self, linter will warn you, forcing a review
Cons:
- Explicit
selfdoesn't reveal capturedself - When reading,
self.adds noise - More typing
Alternatives:
- Review the code in full, with the types involved
The compiler might be patched SR-8536, requiring you to write self in this case. But if you write self everywhere, how does that help?
3.2 Closure
class Style32 {
init() {
some = {
bar()
}
}
var some: Any?
func bar() {}
}
Call to method 'bar' in closure requires explicit 'self.' to make capture semantics explicit
class Style32 {
init() {
self.some = {
self.bar()
}
}
var some: Any?
func bar() {}
}
Since any instance reference will need self captured, compiler now requires explicit self in this case.
Pros of explicit self:
- If you miss
self,lintercompiler emits error, forcing a review
Cons:
- When reading,
self.adds noise - More typing
Pitch: I believe, forcing explicit self in this case was a confused decision. You should be able to tell instance members by other means, see shadowing above. And to convey explicit capture of self there is a cleaner syntax – capture list:
class Style32c {
init() {
some = { [self] in
bar()
}
}
var some: Any?
func bar() {}
}
Forcing explicit self in closures on every member access introduced confusion: if it is required in closures, should I do it everywhere else? Why is it not required everywhere else?
Instead compiler should require explicit self in capture list, and let linter check the rest.
3.3 Auto-Closure
class Style33a {
let bar: Int = 42
func foo() {
nada(bar)
zilch(bar)
}
func nada(_ cb: @autoclosure () -> Int) {}
func zilch(_ cb: @escaping @autoclosure () -> Int) {}
}
Reference to property 'bar' in closure requires explicit 'self.' to make capture semantics explicit
class Style33b {
let bar: Int = 42
func foo() {
self.nada(self.bar)
self.zilch(self.bar)
}
func nada(_ cb: @autoclosure () -> Int) {}
func zilch(_ cb: @escaping @autoclosure () -> Int) {}
}
Pros of explicit self:
- If you miss
self, linter warns you, forcing a review
Cons:
- Explicit
selfdoesn't reveal capturedself - When reading,
self.adds noise - More typing
Pitch: Again, adding explicit self doesn't "make capture semantics explicit". It was a confused decision adding it to the compiler. To make it explicit, we need a different syntax, like:
func foo() {
nada(bar)
zilch([self] bar)
}
3.4 Nested function
class Style34a {
var bar: Int = 0
func foo() {
func niente() {
bar = 42
}
nada(niente)
zilch(niente)
}
func nada(_ cb: () -> Void) {}
func zilch(_ cb: @escaping () -> Void) {}
}
No compiler warnings, but niente captures self.
class Style34b {
var bar: Int = 0
func foo() {
func niente() {
self.bar = 42
}
self.nada(niente)
self.zilch(niente)
}
func nada(_ cb: () -> Void) {}
func zilch(_ cb: @escaping () -> Void) {}
}
Pros of explicit self:
- If you miss
self, linter warns you, forcing a review
Cons:
- Explicit
selfdoesn't reveal capturedself - When reading,
self.adds noise - More typing
Alternatives:
- Replace nested function with a closure
Pitch: Compiler should warn you of captured self, and suggest using a closure with capture list. Or have an extended syntax for nested functions with a capture list, like:
func foo() {
func niente() { [self] in
bar = 42
}
}
Section notes
A compiler/linter warning for a missed self is not to be blindly "fixed" by adding self, it is an opportunity to review that line. So if you type explicit self everywhere, there will be less compiler/linter warnings, triggering less reviews. Explicit self does not mean you have reviewed that line for captured self.
4. Improve readability
4.1 Explicit self always
class Style41a {
var bar: Int = 2
var nada: Int = 3
var baz: Int {
return bar + nada
}
func foo() {
bar = baz + nada
}
}
class Style41b {
var bar: Int = 2
var nada: Int = 3
var baz: Int {
return self.bar + self.nada
}
func foo() {
self.bar = self.baz + self.nada
}
}
Pros of explicit self:
- Easier to reason, you can always tell when it refers to instance member
Cons:
- When reading,
self.adds noise - More typing
4.2 Always self in initializers
Given the common initializer scenario, prefer explicit self on every instance reference in initializer.
class Style42a {
init(bar: Int) {
self.bar = bar
nada = 42
}
let bar: Int
let nada: Int
}
class Style42b {
init(bar: Int) {
self.bar = bar
self.nada = 42
}
let bar: Int
let nada: Int
}
Pros of explicit self:
- Easier to reason, you can always tell when it refers to instance member
- Uniform style easier to read
Cons:
- When reading,
self.adds noise - More typing
IDE Support
Highlight selected symbol
This is very useful, but relies on lexical scope, thus beyond simple text editors.
Syntax highlighting
Have different style for instance and non-instance variables. This relies on lexical scope.
Notify of added/removed shadowing
Pitch: IDE could notify you with a confirmation dialog, or a less intrusive inline warning, or a short-lived highlight.
Discussions
- SE-0009 Require self for accessing instance members
- [Rejected] SE-0009 Require self for accessing instance members
- SwiftLint: Enforce explicit self
- Proposal: Re-instate mandatory self for accessing instance properties and functions (David Hart)
- Proposal: Re-instate mandatory self for accessing instance properties and functions
- Proposal: Allow `[strong self]` capture in closures and remove the `self` requirement therein
- [Review] Require self for accessing instance members
- Proposal SE-0009 Reconsideration
- Should explicit `self.` be required when providing method as closure?
- [Pitch] Improving capturing semantics of local functions
- Explicit capture semantics applied too broadly?
- Revisiting requiring explicit `self.` when passing a method as an escaping closure
- Pitch: Weak method storage modifiers (aka weak references)
- Request explicit capture of self instead of defaulting to a strong capture of self
- Implicit retain cycle


