Understanding `Self`, `self`, and `.self` in Swift (Without Going Crazy)
Understanding Self, self, and .self in Swift (Without Going Crazy)
Swift feels beautifully simple—until you meet Self, self, and .self.
They look almost identical, but they play very different roles.
This post untangles them once and for all.
✅ 1. self — The Current Instance
self is the simplest of the trio.
It refers to the current instance of a type.
You use it when you need to:
(1) Access properties or methods explicitly
struct Person {
var name: String
func printName() {
print(self.name)
}
}
Often Swift lets you omit self, but you must use it when there’s ambiguity:
struct Counter {
var count: Int
mutating func increment(to count: Int) {
self.count = count // without self, this would assign the parameter to itself
}
}
(2) Capture “self” in closures
class ViewController {
func load() {
someAsyncCall { [weak self] in
self?.doSomething()
}
}
}
✔️ TL;DR for self
self= “the current instance”
✅ 2. Self — The Dynamic Type
Capital-S Self refers to the type of the current instance, not the instance itself.
This is incredibly useful when writing:
- protocol requirements
- fluent APIs
- factory methods
- initializers that return the same type
Example: fluent APIs
class Animal {
func clone() -> Self {
return Self.init()
}
required init() {}
}
Here, Self is not Animal.
If you subclass:
class Dog: Animal { }
Then calling:
let d = Dog().clone()
print(type(of: d)) // Dog, not Animal
Swift uses the dynamic type.
Example: Protocols
protocol Copyable {
func copy() -> Self
}
Any type adopting Copyable must return its own type — not just the protocol.
✔️ TL;DR for Self
Self= “the actual runtime type of this instance” Useful for dynamic return types and protocol requirements.
✅ 3. .self — The Value That Represents a Type
.self gives the value of a type itself, not an instance.
This is used:
(1) To get a metatype (type object)
let type: String.Type = String.self
String.self is a value representing the type String—a “metatype.”
Useful in generics or when passing types as arguments:
func createInstance<T>(_ type: T.Type) -> T? {
return type.init()
}
let int = createInstance(Int.self)
(2) To refer to the current instance explicitly
You can also use .self on instances (rare):
let x = 42
print(x.self) // 42
(3) To disambiguate between type and instance members
struct MyStruct {
static let value = 10
let value = 20
func test() {
print(value) // 20
print(Self.value) // 10 (static)
print(self.value) // 20 (instance)
}
}
.self appears here in Self or type references.
✔️ TL;DR for .self
.self= “give me the type as a value” (or “the instance as a value”)
🧠 Quick Summary Table
| Syntax | Meaning | Example |
|---|---|---|
self |
Current instance | self.property, self.method() |
Self |
The dynamic type of the current instance | func clone() -> Self |
.self |
A value that represents either a type or an instance | MyType.self, x.self |
🧪 Examples That Show the Difference Clearly
Example A — self vs Self
class A {
func whoAmI() {
print(Self.self) // prints type A
print(self) // prints instance of A
}
}
Example B — .self with types
let t1 = Int.self // type value
let t2 = String.self
print(t1) // Int
Example C — factory initializer
class Shape {
required init() {}
class func make() -> Self {
return Self.init()
}
}
class Square: Shape { }
let sq = Square.make()
print(type(of: sq)) // Square
🎯 Final Mental Model
self→ the objectSelf→ the type of the objectType.self→ a value that represents a typeinstance.self→ the instance itself as a value
Once you keep those distinctions straight, Swift’s metatype system becomes a superpower instead of a source of confusion.