class UnboundMethod - Documentation for Ruby 3.4
Description: class UnboundMethod: Ruby supports two forms of objectified methods. these method objects are bound to that object.
Keywords: ruby,class,UnboundMethod

# class UnboundMethod

Ruby supports two forms of objectified methods. Class Method is used to represent methods that are associated with a particular object: these method objects are bound to that object. Bound method objects for an object can be created using `Object#method`.

Ruby also supports unbound methods; methods objects that are not associated with a particular object. These can be created either by calling `Module#instance_method` or by calling unbind on a bound method object. The result of both of these is an `UnboundMethod` object.

Unbound methods can only be called after they are bound to an object. That object must be a kind_of? the method’s original class.

    class Square
      def area
        @side * @side
      end
      def initialize(side)
        @side = side
      end
    end

    area_un = Square.instance_method(:area)

    s = Square.new(12)
    area = area_un.bind(s)
    area.call   #=> 144

Unbound methods are a reference to the method at the time it was objectified: subsequent changes to the underlying class will not affect the unbound method.

    class Test
      def test
        :original
      end
    end
    um = Test.instance_method(:test)
    class Test
      def test
        :modified
      end
    end
    t = Test.new
    t.test            #=> :modified
    um.bind(t).call   #=> :original

### Public Instance Methods

meth == other_meth → true or false
Source

    #define unbound_method_eq method_eq

Two unbound method objects are equal if they refer to the same method definition.

    Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice)
    #=> true

    Array.instance_method(:sum) == Enumerable.instance_method(:sum)
    #=> false, Array redefines the method for efficiency

Also aliased as: eql?
arity → integer
Source

    static VALUE
    method_arity_m(VALUE method)
    {
        int n = method_arity(method);
        return INT2FIX(n);
    }

Returns an indication of the number of arguments accepted by a method. Returns a nonnegative integer for methods that take a fixed number of arguments. For Ruby methods that take a variable number of arguments, returns -n-1, where n is the number of required arguments. Keyword arguments will be considered as a single additional argument, that argument being mandatory if any keyword argument is mandatory. For methods written in C, returns -1 if the call takes a variable number of arguments.

    class C
      def one;    end
      def two(a); end
      def three(*a);  end
      def four(a, b); end
      def five(a, b, *c);    end
      def six(a, b, *c, &d); end
      def seven(a, b, x:0); end
      def eight(x:, y:); end
      def nine(x:, y:, **z); end
      def ten(*a, x:, y:); end
    end
    c = C.new
    c.method(:one).arity     #=> 0
    c.method(:two).arity     #=> 1
    c.method(:three).arity   #=> -1
    c.method(:four).arity    #=> 2
    c.method(:five).arity    #=> -3
    c.method(:six).arity     #=> -3
    c.method(:seven).arity   #=> -3
    c.method(:eight).arity   #=> 1
    c.method(:nine).arity    #=> 1
    c.method(:ten).arity     #=> -2

    "cat".method(:size).arity      #=> 0
    "cat".method(:replace).arity   #=> 1
    "cat".method(:squeeze).arity   #=> -1
    "cat".method(:count).arity     #=> -1

bind(obj) → method
Source

    static VALUE
    umethod_bind(VALUE method, VALUE recv)
    {
        VALUE methclass, klass, iclass;
        const rb_method_entry_t *me;
        const struct METHOD *data;
        TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
        convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me, true);

        struct METHOD *bound;
        method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, bound);
        RB_OBJ_WRITE(method, &bound->recv, recv);
        RB_OBJ_WRITE(method, &bound->klass, klass);
        RB_OBJ_WRITE(method, &bound->iclass, iclass);
        RB_OBJ_WRITE(method, &bound->owner, methclass);
        RB_OBJ_WRITE(method, &bound->me, me);

        return method;
    }

Bind umeth to obj. If Klass was the class from which umeth was obtained, `obj.kind_of?(Klass)` must be true.

    class A
      def test
        puts "In test, class = #{self.class}"
      end
    end
    class B < A
    end
    class C < B
    end

    um = B.instance_method(:test)
    bm = um.bind(C.new)
    bm.call
    bm = um.bind(B.new)
    bm.call
    bm = um.bind(A.new)
    bm.call

produces:

    In test, class = C
    In test, class = B
    prog.rb:16:in `bind': bind argument must be an instance of B (TypeError)
     from prog.rb:16

bind_call(recv, args, ...) → obj
Source

    static VALUE
    umethod_bind_call(int argc, VALUE *argv, VALUE method)
    {
        rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
        VALUE recv = argv[0];
        argc--;
        argv++;

        VALUE passed_procval = rb_block_given_p() ? rb_block_proc() : Qnil;
        rb_execution_context_t *ec = GET_EC();

        const struct METHOD *data;
        TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);

        const rb_callable_method_entry_t *cme = rb_callable_method_entry(CLASS_OF(recv), data->me->called_id);
        if (data->me == (const rb_method_entry_t *)cme) {
            vm_passed_block_handler_set(ec, proc_to_block_handler(passed_procval));
            return rb_vm_call_kw(ec, recv, cme->called_id, argc, argv, cme, RB_PASS_CALLED_KEYWORDS);
        }
        else {
            VALUE methclass, klass, iclass;
            const rb_method_entry_t *me;
            convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me, false);
            struct METHOD bound = { recv, klass, 0, methclass, me };

            return call_method_data(ec, &bound, argc, argv, passed_procval, RB_PASS_CALLED_KEYWORDS);
        }
    }

Bind umeth to recv and then invokes the method with the specified arguments. This is semantically equivalent to `umeth.bind(recv).call(args, ...)`.

clone → new_method
Source

    static VALUE
    method_clone(VALUE self)
    {
        VALUE clone;
        struct METHOD *orig, *data;

        TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig);
        clone = TypedData_Make_Struct(CLASS_OF(self), struct METHOD, &method_data_type, data);
        rb_obj_clone_setup(self, clone, Qnil);
        RB_OBJ_WRITE(clone, &data->recv, orig->recv);
        RB_OBJ_WRITE(clone, &data->klass, orig->klass);
        RB_OBJ_WRITE(clone, &data->iclass, orig->iclass);
        RB_OBJ_WRITE(clone, &data->owner, orig->owner);
        RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me));
        return clone;
    }

Returns a clone of this method.

    class A
      def foo
        return "bar"
      end
    end

    m = A.new.method(:foo)
    m.call # => "bar"
    n = m.clone.call # => "bar"

eql?(other_meth) → true or false

Two unbound method objects are equal if they refer to the same method definition.

    Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice)
    #=> true

    Array.instance_method(:sum) == Enumerable.instance_method(:sum)
    #=> false, Array redefines the method for efficiency

Alias for: ==
hash → integer
Source

    static VALUE
    method_hash(VALUE method)
    {
        struct METHOD *m;
        st_index_t hash;

        TypedData_Get_Struct(method, struct METHOD, &method_data_type, m);
        hash = rb_hash_start((st_index_t)m->recv);
        hash = rb_hash_method_entry(hash, m->me);
        hash = rb_hash_end(hash);

        return ST2FIX(hash);
    }

Returns a hash value corresponding to the method object.

See also `Object#hash`.

inspect → string
Source

    static VALUE
    method_inspect(VALUE method)
    {
        struct METHOD *data;
        VALUE str;
        const char *sharp = "#";
        VALUE mklass;
        VALUE defined_class;

        TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
        str = rb_sprintf("#<% "PRIsVALUE": ", rb_obj_class(method));

        mklass = data->iclass;
        if (!mklass) mklass = data->klass;

        if (RB_TYPE_P(mklass, T_ICLASS)) {
            /* TODO: I'm not sure why mklass is T_ICLASS.
             * UnboundMethod#bind() can set it as T_ICLASS at convert_umethod_to_method_components()
             * but not sure it is needed.
             */
            mklass = RBASIC_CLASS(mklass);
        }

        if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
            defined_class = data->me->def->body.alias.original_me->owner;
        }
        else {
            defined_class = method_entry_defined_class(data->me);
        }

        if (RB_TYPE_P(defined_class, T_ICLASS)) {
            defined_class = RBASIC_CLASS(defined_class);
        }

        if (UNDEF_P(data->recv)) {
            // UnboundMethod
            rb_str_buf_append(str, rb_inspect(defined_class));
        }
        else if (RCLASS_SINGLETON_P(mklass)) {
            VALUE v = RCLASS_ATTACHED_OBJECT(mklass);

            if (UNDEF_P(data->recv)) {
                rb_str_buf_append(str, rb_inspect(mklass));
            }
            else if (data->recv == v) {
                rb_str_buf_append(str, rb_inspect(v));
                sharp = ".";
            }
            else {
                rb_str_buf_append(str, rb_inspect(data->recv));
                rb_str_buf_cat2(str, "(");
                rb_str_buf_append(str, rb_inspect(v));
                rb_str_buf_cat2(str, ")");
                sharp = ".";
            }
        }
        else {
            mklass = data->klass;
            if (RCLASS_SINGLETON_P(mklass)) {
                VALUE v = RCLASS_ATTACHED_OBJECT(mklass);
                if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) {
                    do {
                       mklass = RCLASS_SUPER(mklass);
                    } while (RB_TYPE_P(mklass, T_ICLASS));
                }
            }
            rb_str_buf_append(str, rb_inspect(mklass));
            if (defined_class != mklass) {
                rb_str_catf(str, "(% "PRIsVALUE")", defined_class);
            }
        }
        rb_str_buf_cat2(str, sharp);
        rb_str_append(str, rb_id2str(data->me->called_id));
        if (data->me->called_id != data->me->def->original_id) {
            rb_str_catf(str, "(%"PRIsVALUE")",
                        rb_id2str(data->me->def->original_id));
        }
        if (data->me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) {
            rb_str_buf_cat2(str, " (not-implemented)");
        }

        // parameter information
        {
            VALUE params = rb_method_parameters(method);
            VALUE pair, name, kind;
            const VALUE req = ID2SYM(rb_intern("req"));
            const VALUE opt = ID2SYM(rb_intern("opt"));
            const VALUE keyreq = ID2SYM(rb_intern("keyreq"));
            const VALUE key = ID2SYM(rb_intern("key"));
            const VALUE rest = ID2SYM(rb_intern("rest"));
            const VALUE keyrest = ID2SYM(rb_intern("keyrest"));
            const VALUE block = ID2SYM(rb_intern("block"));
            const VALUE nokey = ID2SYM(rb_intern("nokey"));
            int forwarding = 0;

            rb_str_buf_cat2(str, "(");

            if (RARRAY_LEN(params) == 3 &&
                RARRAY_AREF(RARRAY_AREF(params, 0), 0) == rest &&
                RARRAY_AREF(RARRAY_AREF(params, 0), 1) == ID2SYM('*') &&
                RARRAY_AREF(RARRAY_AREF(params, 1), 0) == keyrest &&
                RARRAY_AREF(RARRAY_AREF(params, 1), 1) == ID2SYM(idPow) &&
                RARRAY_AREF(RARRAY_AREF(params, 2), 0) == block &&
                RARRAY_AREF(RARRAY_AREF(params, 2), 1) == ID2SYM('&')) {
                forwarding = 1;
            }

            for (int i = 0; i < RARRAY_LEN(params); i++) {
                pair = RARRAY_AREF(params, i);
                kind = RARRAY_AREF(pair, 0);
                name = RARRAY_AREF(pair, 1);
                // FIXME: in tests it turns out that kind, name = [:req] produces name to be false. Why?..
                if (NIL_P(name) || name == Qfalse) {
                    // FIXME: can it be reduced to switch/case?
                    if (kind == req || kind == opt) {
                        name = rb_str_new2("_");
                    }
                    else if (kind == rest || kind == keyrest) {
                        name = rb_str_new2("");
                    }
                    else if (kind == block) {
                        name = rb_str_new2("block");
                    }
                    else if (kind == nokey) {
                        name = rb_str_new2("nil");
                    }
                }

                if (kind == req) {
                    rb_str_catf(str, "%"PRIsVALUE, name);
                }
                else if (kind == opt) {
                    rb_str_catf(str, "%"PRIsVALUE"=...", name);
                }
                else if (kind == keyreq) {
                    rb_str_catf(str, "%"PRIsVALUE":", name);
                }
                else if (kind == key) {
                    rb_str_catf(str, "%"PRIsVALUE": ...", name);
                }
                else if (kind == rest) {
                    if (name == ID2SYM('*')) {
                        rb_str_cat_cstr(str, forwarding ? "..." : "*");
                    }
                    else {
                        rb_str_catf(str, "*%"PRIsVALUE, name);
                    }
                }
                else if (kind == keyrest) {
                    if (name != ID2SYM(idPow)) {
                        rb_str_catf(str, "**%"PRIsVALUE, name);
                    }
                    else if (i > 0) {
                        rb_str_set_len(str, RSTRING_LEN(str) - 2);
                    }
                    else {
                        rb_str_cat_cstr(str, "**");
                    }
                }
                else if (kind == block) {
                    if (name == ID2SYM('&')) {
                        if (forwarding) {
                            rb_str_set_len(str, RSTRING_LEN(str) - 2);
                        }
                        else {
                            rb_str_cat_cstr(str, "...");
                        }
                    }
                    else {
                        rb_str_catf(str, "&%"PRIsVALUE, name);
                    }
                }
                else if (kind == nokey) {
                    rb_str_buf_cat2(str, "**nil");
                }

                if (i < RARRAY_LEN(params) - 1) {
                    rb_str_buf_cat2(str, ", ");
                }
            }
            rb_str_buf_cat2(str, ")");
        }

        { // source location
            VALUE loc = rb_method_location(method);
            if (!NIL_P(loc)) {
                rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE,
                            RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
            }
        }

        rb_str_buf_cat2(str, ">");

        return str;
    }

Returns a human-readable description of the underlying method.

    "cat".method(:count).inspect   #=> "#<Method: String#count(*)>"
    (1..3).method(:map).inspect    #=> "#<Method: Range(Enumerable)#map()>"

In the latter case, the method description includes the “owner” of the original method (`Enumerable` module, which is included into `Range`).

`inspect` also provides, when possible, method argument names (call sequence) and source location.

    require 'net/http'
    Net::HTTP.method(:get).inspect
    #=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"

`...` in argument definition means argument is optional (has some default value).

For methods defined in C (language core and extensions), location and argument names can’t be extracted, and only generic information is provided in form of `*` (any number of arguments) or `_` (some positional argument).

    "cat".method(:count).inspect   #=> "#<Method: String#count(*)>"
    "cat".method(:+).inspect       #=> "#<Method: String#+(_)>""

Also aliased as: to_s
name → symbol
Source

    static VALUE
    method_name(VALUE obj)
    {
        struct METHOD *data;

        TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
        return ID2SYM(data->me->called_id);
    }

Returns the name of the method.

original_name → symbol
Source

    static VALUE
    method_original_name(VALUE obj)
    {
        struct METHOD *data;

        TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
        return ID2SYM(data->me->def->original_id);
    }

Returns the original name of the method.

    class C
      def foo; end
      alias bar foo
    end
    C.instance_method(:bar).original_name # => :foo

owner → class_or_module
Source

    static VALUE
    method_owner(VALUE obj)
    {
        struct METHOD *data;
        TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
        return data->owner;
    }

Returns the class or module on which this method is defined. In other words,

    meth.owner.instance_methods(false).include?(meth.name) # => true

holds as long as the method is not removed/undefined/replaced, (with private_instance_methods instead of instance_methods if the method is private).

See also `Method#receiver`.

    (1..3).method(:map).owner #=> Enumerable

parameters → array
Source

    static VALUE
    rb_method_parameters(VALUE method)
    {
        return method_def_parameters(rb_method_def(method));
    }

Returns the parameter information of this method.

    def foo(bar); end
    method(:foo).parameters #=> [[:req, :bar]]

    def foo(bar, baz, bat, &blk); end
    method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:req, :bat], [:block, :blk]]

    def foo(bar, *args); end
    method(:foo).parameters #=> [[:req, :bar], [:rest, :args]]

    def foo(bar, baz, *args, &blk); end
    method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:rest, :args], [:block, :blk]]

source_location → [String, Integer]
Source

    VALUE
    rb_method_location(VALUE method)
    {
        return method_def_location(rb_method_def(method));
    }

Returns the Ruby source filename and line number containing this method or nil if this method was not defined in Ruby (i.e. native).

super_method → method
Source

    static VALUE
    method_super_method(VALUE method)
    {
        const struct METHOD *data;
        VALUE super_class, iclass;
        ID mid;
        const rb_method_entry_t *me;

        TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
        iclass = data->iclass;
        if (!iclass) return Qnil;
        if (data->me->def->type == VM_METHOD_TYPE_ALIAS && data->me->defined_class) {
            super_class = RCLASS_SUPER(rb_find_defined_class_by_owner(data->me->defined_class,
                data->me->def->body.alias.original_me->owner));
            mid = data->me->def->body.alias.original_me->def->original_id;
        }
        else {
            super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
            mid = data->me->def->original_id;
        }
        if (!super_class) return Qnil;
        me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass);
        if (!me) return Qnil;
        return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE);
    }

Returns a Method of superclass which would be called when super is used or nil if there is no method on superclass.

to_s → string

Returns a human-readable description of the underlying method.

    "cat".method(:count).inspect   #=> "#<Method: String#count(*)>"
    (1..3).method(:map).inspect    #=> "#<Method: Range(Enumerable)#map()>"

In the latter case, the method description includes the “owner” of the original method (`Enumerable` module, which is included into `Range`).

`inspect` also provides, when possible, method argument names (call sequence) and source location.

    require 'net/http'
    Net::HTTP.method(:get).inspect
    #=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"

`...` in argument definition means argument is optional (has some default value).

For methods defined in C (language core and extensions), location and argument names can’t be extracted, and only generic information is provided in form of `*` (any number of arguments) or `_` (some positional argument).

    "cat".method(:count).inspect   #=> "#<Method: String#count(*)>"
    "cat".method(:+).inspect       #=> "#<Method: String#+(_)>""

Alias for: inspect