Skip to content

self is the implicit receiver

Level: 201 · a reader who writes self. at the start of every Python attribute

One line: Every Ruby call has a receiver, and when you write none it is self — main at the top level, the class inside class … end, the instance inside a method — which is why a bare name reaches a method, while name = v makes a local and a setter needs self.name = v.

self is not a parameter in Ruby; it is whatever object the current code is running as. At the top level that is a special Object instance called main; inside class Person … end it is Person itself (so def self.species defines a method on the class); inside an instance method it is the instance. A call with no receiver — name, puts, secret — is sent to self, which is how a method calls its siblings without naming anything, and how private methods, which forbid an explicit receiver, get called at all. The one place the implicit receiver cannot help is assignment: name = v is always a new local variable, because the parser cannot know you meant the setter, so writing to an attribute from inside the class needs self.name = v — and count = count + 1 is the same trap in a costume.

Verified output of self_is_implicit_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. self at the top level:         main (an instance of Object)
2. self in a class body:          Person (an instance of Class)
3. self in an instance method:    a.whoami -> Person(ann), a.whoami.equal?(a) -> true
4. a receiverless call goes to self: a.greet -> "hi from ann" (greet called name with no receiver)
5. self in def self.species:      a class method: self is Person
6. the setter trap:               a.rename_wrong("bo") -> "bo", but a.name is still "ann" (name = v made a local)
                                  a.rename_right("bo") -> "bo", and a.name is now "bo" (self.name = v called the setter)
7. private methods and self:      secret (bare) -> "the secret of bo"; self.secret -> "the secret of bo"
                                  other.secret -> NoMethodError: private method 'secret' called for an instance of Person
8. count = count + 1:             NoMethodError: undefined method '+' for nil (the local count shadows the reader and starts as nil)
   self.count += 1:               -> 1, c.count is 1; a bare read still works: read_bare -> 1
9. self inside a block:           a.in_block -> [Person(bo)] (the enclosing self, not the block)
10. a top-level def:              top_level_method -> "self here is main"; Object.private_method_defined?(:top_level_method) -> true

Reading the output

Rows 1 to 3 show self in its three homes: main at the top level, the class Person in the class body, the instance inside whoami (which returns the very object it was called on). Row 4 is the receiverless call: greet says name and gets the method, sent to self. Row 5 is def self.species, where self is the class. Row 6 is the trap: rename_wrong writes name = v and only creates a local, so a.name stays "ann"; rename_right writes self.name = v and the setter runs. Row 7: a private method can be called bare, or as self.secret (allowed since Ruby 2.7 (Not machine-checked here.)), but never as other.secret — the error names it as a private method. Row 8 is the trap's most famous costume: count = count + 1 makes count a local for the whole method, so the right-hand side reads that local, which is nil, and nil + 1 fails; self.count += 1 goes through the reader and the setter. Row 9: a block does not get its own self, it keeps the enclosing one. Row 10: a def at the top level becomes a private method of Object, which is why it can be called bare from anywhere.

Compared with Python

Python has no implicit receiver at all, and the twin prints NameError for self at module level and inside a class body (rows 1 and 2). Inside a method, self is just the first parameter — row 3 calls it this and nothing changes — and there is no receiverless dispatch: a bare greet() inside a method is a global lookup (row 4). Class-level code gets cls from @classmethod and nothing from @staticmethod (row 5). Row 6 is the shared trap, arrived at from the other side: name = v is a local in Python too, so self.name = v is required — not because a setter must be disambiguated but because there is simply no other way to reach an attribute. Row 7: _secret is a convention and other._secret() works. Row 8 fails one step earlier than Ruby: assigning count anywhere in the function makes it local everywhere, so reading it first raises UnboundLocalError rather than operating on None. Row 9: a lambda closes over self like any variable; row 10: a top-level def is a module attribute, not a method of anything.

Verified output of self_is_implicit_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. self at module level:          NameError (there is no self here; __name__ is '__main__')
2. self in a class body:          NameError (the body sees __qualname__ = 'Person')
3. self is the first parameter:   a.whoami() -> Person(ann) ; a.whoami() is a -> True ; Person.greet(a) -> 'hi from ann' (named this, not self)
4. no receiverless call:          a.greet_bare() -> NameError (a bare greet() is a global lookup)
5. cls, or nothing:               a classmethod: cls is Person; a staticmethod: neither self nor cls
6. the setter trap:               a.rename_wrong('bo') -> 'bo' but a.name is still 'ann' (name = v made a local)
                                  a.rename_right('bo') -> 'bo' and a.name is now 'bo' (self.name = v set the attribute)
7. private is a convention:       a._secret() -> 'the secret of bo' ; a.peek(Person('cy')) -> 'the secret of cy' (nothing enforced)
8. count = count + 1:             UnboundLocalError (the assignment makes count a local for the whole method, read before it is set)
   self.count += 1:               -> 1 ; c.count is 1
9. self inside a lambda:          a.in_lambda() is a -> True (a closed-over variable, like any other)
10. a top-level def:              'top_level_function' in globals() -> True (a module attribute, not a method of anything)

Try it

  1. Write a Counter with attr_accessor :count and four methods: bump_wrong using count = count + 1, bump_right using self.count += 1, bump_ivar using @count += 1, and read_bare returning count. Call each and print what happens, including the class and message of the error from the first.
  2. Write Temperature with attr_accessor :celsius and a setter fahrenheit=(f) that stores (f - 32) * 5 / 9.0 into celsius. Inside another method, call the setter once as fahrenheit = 212 and once as self.fahrenheit = 212, and print celsius after each to see which one reached the object.
Solution to 1

self_is_implicit_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: the `count = count + 1` trap. Reading `count` bare reaches the reader;
# assigning `count = ...` makes a local for the whole method, so a setter
# needs self.
class Counter
  attr_accessor :count
  def initialize = @count = 0

  def bump_wrong
    count = count + 1
  end

  def bump_right
    self.count += 1
  end

  def bump_ivar
    @count += 1
  end

  def read_bare = count
end

c = Counter.new
begin
  c.bump_wrong
rescue NoMethodError => e
  puts "1. bump_wrong  -> #{e.class}: #{e.message}"
end
puts "   (count = count + 1 makes count a local for the whole method, so the right-hand count is that local, nil)"
puts "2. bump_right  -> #{c.bump_right} (self.count += 1 reads through the reader and writes through the setter)"
puts "3. bump_ivar   -> #{c.bump_ivar} (@count += 1 skips both)"
puts "4. read_bare   -> #{c.read_bare} (a bare read still reaches the reader)"
puts "5. c.count     -> #{c.count}"

Verified output of self_is_implicit_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. bump_wrong  -> NoMethodError: undefined method '+' for nil
   (count = count + 1 makes count a local for the whole method, so the right-hand count is that local, nil)
2. bump_right  -> 1 (self.count += 1 reads through the reader and writes through the setter)
3. bump_ivar   -> 2 (@count += 1 skips both)
4. read_bare   -> 2 (a bare read still reaches the reader)
5. c.count     -> 2

See also