Ruby 2.7 adds UnboundMethod#bind_call method

2 minute read

Ruby supports two forms of methods:

Bound Methods: These methods are associated to an object.

Unbound Methods: These methods are not associated to any particular object.

Let’s say we have a User class.

# user.rb class User def initialize ( name ) @name = name end def name @name end def welcome "Hi, #{ self . name } " end end

> user = User . new ( 'John Doe' ) # Bound Method > user . method ( :welcome ) #=> #<Method: User#welcome (SOME_PATH/user.rb):6> # Unbound Method > User . instance_method ( :welcome ) #=> #<UnboundMethod: User#welcome (SOME_PATH/user.rb):6>

We can create UnboundMethod using Module#instance_method or Method#unbind and can call after binding to an object.

What is binding?

A Binding object contains the execution context in the code. The execution context consists of variables, methods, the value of self and block. This context can be later accessed using binding function.

For example:

# person.rb class Person def initialize ( name ) @name = name end def get_binding binding end end

Now try to access name from the instance of Person class.

> person = Person . new ( 'John Doe' ) #<Person:0x00007fac42439fe0 @name="John Doe"> > person . name #=> NoMethodError (undefined method 'name' for #<Person:0x00007fac42439fe0 @name="John Doe">)

We can access the name instance variable using eval . This method takes the code as the first argument and binding as the second argument.

> eval ( '@name' , person . get_binding ) #=> "John Doe" > person . get_binding . receiver #<Person:0x00007fac42439fe0 @name="John Doe">

We use Method#bind to bind an object to UnboundMethod . But the allocation from bind is quite expensive.

That is why Ruby 2.7 has added UnboundMethod#bind_call to avoid the intermediate allocation.

Let’s say we have a Manager class which is inherited from User class.

class Manager < User def welcome "Hi Manager, #{ self . name } " end end

To call UnboundMethod :

Before Ruby 2.7

> manager = Manager . new ( 'John Doe' ) # Calling an unbound method > User . instance_method ( :welcome ). bind ( manager ). call #=> "Hi, John Doe"

Ruby 2.7

> manager = Manager . new ( 'John Doe' ) # Calling an unbound method > User . instance_method ( :welcome ). bind_call ( manager ) #=> "Hi, John Doe"

Apart from object, we can also pass parameters to bind_call similar to call method.

class User def welcome ( name ) "Hi, #{ name } " end end > user = User . new > User . instance_method ( :welcome ). bind_call ( user , 'John Doe' ) #=> "Hi, John Doe"

Following are benchmark results:

# Student class class Student def score end end > student = Student . new > N = 100000 > Benchmark . bmbm do | x | > x . report ( "bind.call" ) { N . times { Student . instance_method ( :score ). bind ( student ). call }} > x . report ( "bind_call" ) { N . times { Student . instance_method ( :score ). bind_call ( student ) }} > end # # Rehearsal --------------------------------------------------- # bind.call 0.066435 0.000766 0.067201 ( 0.067263) # bind_call 0.042495 0.000049 0.042544 ( 0.042587) # ------------------------------------------ total: 0.109745sec # # user system total real # bind.call 0.063620 0.000201 0.063821 ( 0.063870) # bind_call 0.040765 0.000025 0.040790 ( 0.040812)