Does instance method take an implicit self parameter in Swift?

I used Python a lot before. So I somehow take it for granted that instance method receives an implicit argument which refers to the instance. If I remember it correctly, I think this is true for C++ instance method also.

However, I realize it may not be the case in Swift when I try to understand how class and protocol work together. Take the following code for exammple,

import Foundation

protocol MyProtocol {
    func greeting(who: String)
}

extension MyProtocol {
    func greeting(who: String) {
        print("Hello, \(who)")
    }
}

class MyClass: MyProtocol {
}

let o = MyClass()
o.greeting(who: "World")

The greeting() function is a plain old function implemented in protocol extension. It takes a single parameter who. Since o.greeting() just works fine, it suggests that Swift compiler doesn't pass self to instance method.

So, if self is not passed to instance method as a parameter, how is the instance method able to access it?

I find the following in the doc:

Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.

I have a hard time in understanding what it exactly means. I mean, if self is a property, then we need to access the instance first to get that property, right? But then we come back to the original question - how does instance method get a reference to the instance? Is that just hardcoded when compiler compiles the code? But if so, why can't we just understand self as a reference to the instance?

I guess this question was probably asked before but I'm not able to find any discussion about it. Thanks for any explanation.

self is passed to instance methods: it is passed implicitly by the compiler. You do not write it explicitly in source code at the declaration nor the call site. It is implicit.

However, you can see that it is there:

struct S {
  func foo() {}
}

let f = S.foo
print(type(of: f))    // (S) -> () -> ()

let s = S()
let g = s.foo
print(type(of: g))    // () -> ()

let h = f(s)
print(type(of: h))    // () -> ()
6 Likes

Thanks for the great example! The behavior seems quite similar to unbound and bound functions in Python.

After seeing your example, I realize my above conclusion was wrong. Thanks!

After seeing your example, I'm completely convinced that instance method can access self somehow. But perhaps it's through closure, instead of function parameter? Otherwise the o.greating(who: "World") call wouldn't work because greeting() is defined in protocol extension and can't take an additional implicit parameter.

You seem to have a fundamental misunderstanding about how method calls work in Swift.

Instance methods always take a self parameter. The syntax x.foo() is effectively sugar for T.foo(self: x)().

In your example:

let f = MyClass.greeting
print(type(of: f))
// (MyClass) -> (String) -> ()

You can also do the same thing in a generic context:

func foo<T: MyProtocol>(_ t: T) {
  print(type(of: T.greeting))
}

let o = MyClass()
foo(o)
2 Likes

Thanks for the explanation. I finally figured out where I got confused. I didn't realize methods defined in protocol extension behave in a similar way as methods in class or structs. I somehow considered them as global functions in the past! That's why I had difficulties in understanding how protocol and class work together.

print(type(of: MyProtocol.greeting)) 
// (MyProtocol) -> (String) -> ()

Thanks!

I'd just chime in: Python is a little odd in this regard (compared to other common OO languages like Ruby, JS, Java, C#, C++, ...)

Notice that in Python, you add self as an explicit parameter at the site of the method declaration:

class Foo:
    def my_method(self, arg1, arg2):
        print(self, arg1, arg2)

But not at the call site:

my_object = Foo()
my_object.my_method("arg 1", "arg 2")

Notice it's not:

my_object.my_method(my_object, "arg 1", "arg 2") // ❌

So even Python has this same "self is passed in implicitly for you" behavior.

Interestingly, you can actually give the first parameter of a method any name you want (it's doesn't need to be self), e.g. you could use the Java/C#/C++ convention of this instead, if you wanted. But don't do that: it's not pythonic.

I think this is true for C++ instance method also.

Nope, C++ methods have an implicit this, which is a pointer to the receiver of the method call. It's not declared at the method declaration or definition, nor passed explicitly at the method call site.

2 Likes

But you can do it explicitly this way:

Foo.my_method(my_object, "arg 1", "arg 2")

I guess the rational behind the design is that Python tries to make it explicit that methods are just functions.

Thanks. I was aware of that but didn't express myself clearly. When I posted my question, what I tried to figure out was if self was passed as a function parameter. I wasn't sure because a) I never find any document discussing this (in contrast, there are a lot of materials explaining how this works in C++), and b) I misunderstood how the method in protocol extension works.