Привет всем.
Решил изучать исходники моего любимого Руби. Буду периодически делиться с вами своими наблюдениями.
Небольшое предупреждение:
Желательно знать Си. Хотя бы немного. Ладно-ладно, не знаете? Ну, тогда спрашивайте в комментариях, я вам на «яблоках» объясню (и почему я постоянно думаю, что кто-то вообще еще продолжает читать этот рассказ?).
Пожалуйста, не пугайтесь слова «указатель». Это переменная которая указывает на что-то другое (если выражаться очень понятным языком).
Если попытаться привести простую аналогию указателей в руби, то на ум приходит следующая конструкция:
Есть объект стандартного удостоверения личности. А есть человек, имеющий этот документ.
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 «заполняется» методами своего родителя, я переживал за память.
Теперь мне легче. Умнеть иногда полезно =)
Думаю, теперь вы понимаете, что при наследовании руби создает цепочку классов, и каждый класс из этой цепи ссылается на свою таблицу методов.
Руби будет искать метод до тех пор, пока не дойдет до самого высшего класса цепочки.
В следующем выпуске я хотел бы ближе рассмотреть модули, то как они включаются в цепь наследования, а так же получше рассмотреть таблицы методов и переменных объекта.
Спасибо за терпение, жду ваших отзывов.

{ 31 comments… read them below or add one }
Никаких особо новых идей, если вы знакомы с рантаймом ObjC. То что класс – это объект, во всяком случае, вполне привычно ^_^
Будем тогда надеятся, что эта статья кому-нибудь пригодится =)
Я и до изучения сорсов знал, что класс это тоже объект. Но вот увидеть это всё изнутри, как мне кажется, еще интереснее.
Спасибо за комментарий.
Очень хорошая презентация Ruby Internals http://mwrc2008.confreaks.com/11farley.html
А так, продолжайте, дальше должны быть интересные вещи!
@cypok
Спасибо большое =)
Спасибо! Весьма интересно!
Замечательно :) прочитал с удовольствием, показанные вами структуры RObject, RClass включающие в себя RBasic мне многое объяснили.
Пишите еще.
@ignar
Спасибо за отличный отзыв =)
Статья интересная, но немного сумбурная. Могу посоветовать книгу от PragProg: http://pragprog.com/titles/ppmetr/metaprogramming-ruby. Там описано поведение класса, объекта (для 1.9 ещё и BasicObject) и модуля. Но за исследование исходников руби 5+. Копайте дальше (как насчёт jRuby?!). :)
Спасибо, это мой первый опыт в подобном деле =)
Буду и дальше копать. Тема практически безгранична =)
С JRuby так себе, практически никак.
Непонятно какие флаги могут быть в RBasic. Непонятно из приведенных структур почему iv_tbl не добавили в RBasic.
Спасибо, обязательно продолжай! Пользуемся-то мы этим делом ежедневно, а разбираться что внутри как обычно лень.
@Nikolay Karev
Спасибо еще раз, буду продолжать по мере обучение =)
Настораживает, что у объекта нет таблицы методов:
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?
class A; end
a = A.new
a.instance_eval «def hello; puts ‘goodbye’; end»
a_singleton_class = class < ‘goodbye’
b = A.new
b.hello
парсер съел код
метод ‘hello’ попадает в singleton class объекта а
a_singleton_class = class << a; self; end
puts a_singleton_class.instance_methods.include?('hello')
Отличная статья!
За статью спасибо, надеюсь это только верхушка айзберга :). Мне, например, особенно интересно было бы узнать, какой подход использовать для увеличения производительности в тех или иных ситуациях. Всем известно, что в ruby одно и то же можно сделать несколькими способами, но как и везде есть ньюансы :).
@saksmiz
Конечно, всё только начинается. Вы обязательно всё узнаете, а я обязательно буду стараться хорошо объяснить.
Так или иначе мы придём к написанию расширений на Си для руби =)
Руби отличный язык, и он имеет отличное апи на Си, поэтому писать особо критичные участки на си очень просто.
О! Это было бы отлично! После таких слов, я буду с нетерпением ждать новых постов на эту тему.
Статья вовсе не «отличная». Все очень сумбурно.
@Sergey, спасибо за отзыв. буду работать над этем.
@Koterpillar, в следующей статье я буду подробно рассказывать о ghost-классах, тогда вы поймете, почему метод hello попал только в объект a.
Спасибо большое!
Ждем продолжения.
>> Пожалуйста, не пугайтесь слова «указатель». Это простая ссылка на что-то.
Не путайте указатель и ссылку, в С ссылок нет.
>> Первое, что нужно понять, любой класс в руби имеет свой номер.
Не-а, классы не пронумерованы.
>> В руби такой идентификатор обозначается словом VALUE, стоит понимать, что это – обычное число.
VALUE — это указатель, если посмотреть макросы и функции, которым передаются параметры типа VALUE, то там они преобразуются к типа RBasic* и прочим.
P.S. А чего Ruby 1.8, а не Ruby 1.9?
@O01eg
Спасибо за отличный отзыв.
Я уже много спорил о ссылках и указателях. Я понимаю, что ссылка != указатель.
В любом случае, я исправил это.
И с VALUE глупо получилось, согласен.
Я просто не очень часто пользовался приемом:
insugned long address;
void* ptr = malloc(sizeof(SomeStruct));
address = (SomeStruct*) ptr;
address -> field = «hello»;
поэтому для меня это было новым =)
четко, пиши еще
Беглый обзор статьи говорит о том, что, читать нужно. Пподолжай в том же духе!
«Выведет nil, так как @my_var»
@var
@my_var необъявлен
Адрес класса
irb(main):001:0> class A; end
=> nil
irb(main):002:0> d = A.new
=> #
Статья хорошая, Struct не использовал, спасибо за наводку :)
Ruby Hacking Guide как раз об этом.
К сожалению, оно не переведено целиком даже на английский, но то, что переведено, как раз о том же, о чём пишется в статье.
http://rhg.rubyforge.org/
@kaineer
Я узнал о RHG уже после того, как написал эту статью =)
Если объекты не имеют своей таблицы символов, то тогда каким образом реализованы так называемые Singletoh-методы?
Кто нибудь может это объяснить?
{ 2 trackbacks }