Изучаем ядро Ruby — классы и объекты

33 comments

in Разработка

Привет всем.
Решил изучать исходники моего любимого Руби. Буду периодически делиться с вами своими наблюдениями.

Небольшое предупреждение:
Желательно знать Си. Хотя бы немного. Ладно-ладно, не знаете? Ну, тогда спрашивайте в комментариях, я вам на «яблоках» объясню (и почему я постоянно думаю, что кто-то вообще еще продолжает читать этот рассказ?).

Пожалуйста, не пугайтесь слова «указатель». Это переменная которая указывает на что-то другое (если выражаться очень понятным языком).
Если попытаться привести простую аналогию указателей в руби, то на ум приходит следующая конструкция:


Есть объект стандартного удостоверения личности. А есть человек, имеющий этот документ.

class Document
  attr_accessor :person
 
  # при создании документа указываем его номер а так же объект, которому будет принадлежать сей документ
  def initialize(object, number)
    @document_number = number
    @person = object
  end
end
 
Person = Struct.new(:name)
 
# наш человек имеет имя Олег!
someone = Person.new("Oleg")
 
# теперь создадим документ
# передадим в качестве аргументов функции нашего человека и номер документа
doc = Document.new(someone,23234)
 
# а теперь магия!
puts doc.person.name
# => "Oleg"
doc.person.name = "Natalia"
puts doc.person.name
# => "Natalia"

На самом деле при создании нашего документа мы передали УКАЗАТЕЛЬ на объект Person. Таким образом получили возможность манипулировать этим объектом через класс Document.

В си нет классов, зато есть Структуры. И в руби они тоже есть (если вы не знали, то смотрим внимательно на класс Struct).
Я начну свой рассказ с файла «ruby.h», и не от того, что я уже как пять пальцев знаю всю архитектуру руби, а, наверное, из-за того, что именно этот файл мы инклудим при создании своего расширения на си для руби.

Обратим внимание на самые важные вещи для нас -> класс и объект. Сейчас вы поймёте почему метапрограммирование в руби именно такое, почему можно сделать так и оно будет работать:

class MyClass
  def self.hello
    puts "hello"
  end
end 
 
MyClass.hello
# >> "hello"
 
MyClass.instance_eval {
  def hello
    puts "goodbye"
  end
}
 
MyClass.hello
# >> goodbye

Для себя я выделил 3 структуры, которые будут интересны нам:
RBasic, RObject и RClass.

Вот код этих структур:

struct RBasic {
unsigned long flags;
VALUE klass;
};
 
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
 
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};

Давайте внимательно взглянем на структуры RObject и RClass (Объект и Класс).
Наверное, вы обратили внимание, что обе структуры имеют внутри себя еще одну структуру — RBasic.
Структура RBasic содержит в себе интересную строку — «VALUE klass».  Если узнать, что же такое VALUE, то оказывается, что это переменная типа unsigned long. Эта переменная хранит в себе адрес класса в памяти. Зная адрес класса в памяти мы можем обращаться к нему в любое время!

Объект и класс в руби, обязательно имеют VALUE klass. Понимаете к чему я веду? Класс тоже является объектом!

Да, это странно, и не похоже на некоторые другие языки.
Именно это когда-то вскрыло мне мозги — »О боже, как так? Декларация класса тоже объект? no way!»

Давайте приглядимся к структуре RObject, помимо структуры RBasic она имеет ссылку на iv_tbl.
iv_tbl расшифровывается как instance_variable_table, то есть таблица переменных объекта.

Класс тоже имеет ссылку на таблицу переменных объекта.
Да, да, у класса тоже могут быть свои переменные =) Эти переменные будут преследовать класс на протяжении всего цикла работы программы.

class MyClass; end
MyClass.instance_variable_set("@var", 100)
puts MyClass.instance_variable_get("@var")
# >> 100
obj = MyClass.new()
puts obj.instance_variable_get("@var")
# >> nil

Выведет nil, так как @my_var является переменной объекта MyClass, а не переменной экземпляра класса MyClass. (Надеюсь, я не запутал вас)

Окей, мы попытались разобраться с iv_tbl ( таблица переменных объекта ), теперь попробуем перейти к m_tbl.
Это указатель на таблицу методов (methods_table). Вы, наверное, уже заметили, что объект не имеет в себе указатель на таблицу методов.

Почему? Всё очень просто. Объект имеет внутри себя структуру RBasic, которая содержит в себе адрес своего класса.

Как происходит поиск методов?

Предположим мы написали такой код:

object.my_method()

Интерпретатор обращается к объекту, узнает адрес класса объекта, обращается к классу по этому адресу. Найдя класс, интерпретатор обращается к таблице методов класса и ищет там метод.

Вы уже заметили строку «VALUE super» в структуре RClass?
Здесь содержится адрес родительского класса.

если мы сделаем что-то вроде:

class A; end
class B < A; end

То теперь в поле «VALUE super» класса B будет адрес класса A, так как класс A является родителем класса B.

Таким образом, когда мы вызываем метод объекта, интерпретатор узнает класс объекта, обращается к этому классу, узнает адрес таблицы методов и пытается найти метод в этой таблице. Если метод не был найден, интерпретатор узнает адрес класса-родителя и если такой имеется, то он продолжает поиск метода в таблице методов класса-родителя.
Если метод не найден и в классе-родителе, то поиск продолжается в super классе нашего родителя:

class A
def my_method()
end
end
class B < A; end
class C < B; end
 
c = C.new()
 
c.my_method()

Мы видим, что метод my_method() определен в классе A, таким образом интерпретатор сначала будет искать этот метод в классе C, затем перейдет к классу B, а затем к классу A.
Это интересно, потому как раньше мне казалось, что создавая вот такое наследование:

class User < ActiveRecord::Base
end

мы тянем за собой всю тонну классов ActiveRecord’а. Мне было страшно, что мой скромный класс User «заполняется» методами своего родителя, я переживал за память.
Теперь мне легче. Умнеть иногда полезно =)

Думаю, теперь вы понимаете, что при наследовании руби создает цепочку классов, и каждый класс из этой цепи ссылается на свою таблицу методов.
Руби будет искать метод до тех пор, пока не дойдет до самого высшего класса цепочки.

В следующем выпуске я хотел бы ближе рассмотреть модули, то как они включаются в цепь наследования, а так же получше рассмотреть таблицы методов и переменных объекта.

Спасибо за терпение, жду ваших отзывов.

Progg it

{ 31 comments… read them below or add one }

1 Farcaller Июль 5, 2010 в 20:46

Никаких особо новых идей, если вы знакомы с рантаймом ObjC. То что класс – это объект, во всяком случае, вполне привычно ^_^

Ответить

2 pechorin-andrey Июль 5, 2010 в 21:15

Будем тогда надеятся, что эта статья кому-нибудь пригодится =)
Я и до изучения сорсов знал, что класс это тоже объект. Но вот увидеть это всё изнутри, как мне кажется, еще интереснее.
Спасибо за комментарий.

Ответить

3 cypok Июль 5, 2010 в 21:57

Очень хорошая презентация Ruby Internals http://mwrc2008.confreaks.com/11farley.html

А так, продолжайте, дальше должны быть интересные вещи!

Ответить

4 pechorin-andrey Июль 5, 2010 в 22:29

@cypok
Спасибо большое =)

Ответить

5 Иван Июль 6, 2010 в 00:07

Спасибо! Весьма интересно!

Ответить

6 ignar Июль 6, 2010 в 00:33

Замечательно :) прочитал с удовольствием, показанные вами структуры RObject, RClass включающие в себя RBasic мне многое объяснили.
Пишите еще.

Ответить

7 pechorin-andrey Июль 6, 2010 в 00:58

@ignar
Спасибо за отличный отзыв =)

Ответить

8 Dmitry Июль 6, 2010 в 01:32

Статья интересная, но немного сумбурная. Могу посоветовать книгу от PragProg: http://pragprog.com/titles/ppmetr/metaprogramming-ruby. Там описано поведение класса, объекта (для 1.9 ещё и BasicObject) и модуля. Но за исследование исходников руби 5+. Копайте дальше (как насчёт jRuby?!). :)

Ответить

9 pechorin-andrey Июль 6, 2010 в 01:34

Спасибо, это мой первый опыт в подобном деле =)
Буду и дальше копать. Тема практически безгранична =)

С JRuby так себе, практически никак.

Ответить

10 Igor Июль 6, 2010 в 02:28

Непонятно какие флаги могут быть в RBasic. Непонятно из приведенных структур почему iv_tbl не добавили в RBasic.

Ответить

11 Nikolay Karev Июль 6, 2010 в 07:33

Спасибо, обязательно продолжай! Пользуемся-то мы этим делом ежедневно, а разбираться что внутри как обычно лень.

Ответить

12 pechorin-andrey Июль 6, 2010 в 12:53

@Nikolay Karev
Спасибо еще раз, буду продолжать по мере обучение =)

Ответить

13 Koterpillar Июль 6, 2010 в 08:03

Настораживает, что у объекта нет таблицы методов:
class A
end
a = A.new
a.instance_eval «def hello; puts ‘goodbye’; end»
a.hello # => ‘goodbye’
b = A.new
b.hello # => NoMethodError: undefined method `hello’ for #

В какую таблицу попал hello?

Ответить

14 trybeee Июль 21, 2010 в 21:19

class A; end
a = A.new
a.instance_eval «def hello; puts ‘goodbye’; end»
a_singleton_class = class < ‘goodbye’
b = A.new
b.hello

Ответить

15 trybeee Июль 21, 2010 в 21:20

парсер съел код
метод ‘hello’ попадает в singleton class объекта а
a_singleton_class = class << a; self; end
puts a_singleton_class.instance_methods.include?('hello')

Ответить

16 zick Июль 6, 2010 в 08:28

Отличная статья!

Ответить

17 saksmlz Июль 6, 2010 в 11:45

За статью спасибо, надеюсь это только верхушка айзберга :). Мне, например, особенно интересно было бы узнать, какой подход использовать для увеличения производительности в тех или иных ситуациях. Всем известно, что в ruby одно и то же можно сделать несколькими способами, но как и везде есть ньюансы :).

Ответить

18 pechorin-andrey Июль 6, 2010 в 12:57

@saksmiz
Конечно, всё только начинается. Вы обязательно всё узнаете, а я обязательно буду стараться хорошо объяснить.
Так или иначе мы придём к написанию расширений на Си для руби =)

Руби отличный язык, и он имеет отличное апи на Си, поэтому писать особо критичные участки на си очень просто.

Ответить

19 saksmlz Июль 14, 2010 в 20:03

О! Это было бы отлично! После таких слов, я буду с нетерпением ждать новых постов на эту тему.

Ответить

20 Sergey Июль 6, 2010 в 12:37

Статья вовсе не «отличная». Все очень сумбурно.

Ответить

21 pechorin-andrey Июль 6, 2010 в 12:50

@Sergey, спасибо за отзыв. буду работать над этем.

Ответить

22 pechorin-andrey Июль 6, 2010 в 12:53

@Koterpillar, в следующей статье я буду подробно рассказывать о ghost-классах, тогда вы поймете, почему метод hello попал только в объект a.

Ответить

23 shaliko Июль 6, 2010 в 13:11

Спасибо большое!

Ждем продолжения.

Ответить

24 O01eg Июль 6, 2010 в 14:34

>> Пожалуйста, не пугайтесь слова «указатель». Это простая ссылка на что-то.
Не путайте указатель и ссылку, в С ссылок нет.
>> Первое, что нужно понять, любой класс в руби имеет свой номер.
Не-а, классы не пронумерованы.
>> В руби такой идентификатор обозначается словом VALUE, стоит понимать, что это – обычное число.
VALUE — это указатель, если посмотреть макросы и функции, которым передаются параметры типа VALUE, то там они преобразуются к типа RBasic* и прочим.
P.S. А чего Ruby 1.8, а не Ruby 1.9?

Ответить

25 pechorin-andrey Июль 7, 2010 в 14:24

@O01eg
Спасибо за отличный отзыв.
Я уже много спорил о ссылках и указателях. Я понимаю, что ссылка != указатель.
В любом случае, я исправил это.

И с VALUE глупо получилось, согласен.
Я просто не очень часто пользовался приемом:
insugned long address;
void* ptr = malloc(sizeof(SomeStruct));
address = (SomeStruct*) ptr;
address -> field = «hello»;

поэтому для меня это было новым =)

Ответить

26 randx Июль 6, 2010 в 14:45

четко, пиши еще

Ответить

27 woto Июль 6, 2010 в 18:08

Беглый обзор статьи говорит о том, что, читать нужно. Пподолжай в том же духе!

Ответить

28 Роман Июль 8, 2010 в 20:28

«Выведет nil, так как @my_var»
@var
@my_var необъявлен

Адрес класса

irb(main):001:0> class A; end
=> nil
irb(main):002:0> d = A.new
=> #

Статья хорошая, Struct не использовал, спасибо за наводку :)

Ответить

29 kaineer Июль 14, 2010 в 20:25

Ruby Hacking Guide как раз об этом.
К сожалению, оно не переведено целиком даже на английский, но то, что переведено, как раз о том же, о чём пишется в статье.
http://rhg.rubyforge.org/

Ответить

30 pechorin-andrey Июль 14, 2010 в 21:00

@kaineer
Я узнал о RHG уже после того, как написал эту статью =)

Ответить

31 ascrazy Январь 9, 2011 в 14:04

Если объекты не имеют своей таблицы символов, то тогда каким образом реализованы так называемые Singletoh-методы?
Кто нибудь может это объяснить?

Ответить

Leave a Comment

{ 2 trackbacks }

Previous post:

Next post: