Я отвечаю на свой вопрос здесь
Есть ли скрытое свойство, которое позволяет Ruby отличать модули от классов?
Действительно, есть. Внутри все Ruby объекты начинаются со структуры с именем RBasic
:
struct RBasic {
VALUE flags;
const VALUE klass;
};
В пределах RBasic
у нас есть flags
, и эти флаги содержат информацию о типе:
enum ruby_value_type {
RUBY_T_NONE = 0x00,
RUBY_T_OBJECT = 0x01,
RUBY_T_CLASS = 0x02,
RUBY_T_MODULE = 0x03,
RUBY_T_FLOAT = 0x04,
RUBY_T_STRING = 0x05,
// ...
RUBY_T_MASK = 0x1f
};
И вот что в конечном итоге проверяет Ruby при выполнении проверки типа: Marshal
также используется *1023* для вывода информации о типе:
module M ; end
class C ; end
Marshal.dump(M) #=> "\x04\bm\x06M"
Marshal.dump(C) #=> "\x04\bc\x06C"
Marshal.dump(4) #=> "\x04\bi\t"
# ^
# m = module, c = class, i = integer
Изнутри Ruby мы можем проверить внутренний тип с помощью Fiddle :
require 'fiddle'
def type(obj)
struct = Fiddle::Pointer.new(obj.object_id << 1)
flags = struct[0]
flags & 0x1f
end
module M ; end
class C ; end
type(M) #=> 3 (RUBY_T_MODULE = 0x03)
type(C) #=> 2 (RUBY_T_CLASS = 0x02)
И поскольку Fiddle также позволяет изменять базовые данные, мы, вероятно, могли бы превратить класс в модуль, изменив его Соответствующие флаги ...
Давайте попробуем: ![image](https://i.stack.imgur.com/hJm5R.png)
class C
def hello
'hello from class'
end
end
class Foo
end
Foo.include(C)
#=> TypeError: wrong argument type Class (expected Module)
Теперь тип меняется с 0x02
(класс) на 0x03
(модуль):
require 'fiddle'
struct = Fiddle::Pointer.new(C.object_id << 1)
struct[0] = (struct[0] & ~0x1f) | 0x03
Foo.include(C)
# NoMethodError: undefined method `append_features' for C:Class
Все еще ошибка, но Ruby больше не жалуется на тип!
Очевидно, Class
не определяет Module#append_features
, потому что метод не имеет большого смысла для классов , Давайте переопределим его для C
:
C.define_singleton_method(:append_features, Module.instance_method(:append_features))
Foo.include(C)
# no error!
Foo.ancestors
#=> [Foo, C, Object, BasicObject, Object, Kernel, BasicObject]
Foo.new.hello
#=> "hello from class"
И вот мы go: класс, включенный в другой класс.
Примечание: я возился с внутренностями Ruby Вот. Не используйте этот вид хаков в производстве. Вы были предупреждены.