 Hello, everybody. My name is Jin Tee. I'm a final year student from the Singapore University of Technology and Design. I was introduced to the world of Ruby and Rails about slightly more than a year ago. By way of a very short self-introduction, this is my exceedingly silly get-up handle. This may or may not be my typical facial expression, IRL. My talk today is called, all I wanted to know about Ruby's object model starting out. And more. But before I dive into the topic proper, I want to talk a little bit about the motivations behind it. So, now, why this topic and why this talk? Well, if you ask me why this talk, the first thing I'm going to say to you is, free RDRC tickets, yay! Of course, I also want to approach this more as a beginner who still has a lot to learn about programming and the language and software development in general. So, the question that I have is, what is it like to be a beginner learning Ruby? So, in the beginning, everything is lovely and beautiful, right? The language is clean, clutter-free, expressive, sometimes even reads just like English. Blocks, procs, enumerators and lambdas, slightly strange, but you get the hang of it after a while. And the basic object-oriented paradigm isn't too hard to pick up, so pretty soon you're writing it on classes, sub-classing them, mixing in modules. And so, you are in love. But if you hang around with Ruby long enough, you start to see fun stuff like this and this. And you hear people talking about singleton methods, meta-classes, mixing and extending modules instead of including them and so on and so forth and a lot of hate-scratching and confusion and sews. And maybe you keep reading and re-reading Ruby docs and various blog posts, but you still can't quite remember the difference between class-eval or instance-eval, or you still keep wondering, what does that self-dot included hook or why it's even necessary? And so, you begin to realise that you should be able to do better. You should be able to build a mental model that has the power of unifying all these seemingly disparate bits of Ruby. And so, that's why I sell out to do and that is basically all I wanted to know about Ruby's object model starting out. But because I also have completionist tendencies, I decided to poke around the CRuby source code itself and that is where the more comes in. So, without further ado, here's all I wanted to know about Ruby's object model starting out as a story. In the beginning, there was chaos, but soon from that primordial soup of procedural code that sprung for type devs and macros and these gradually coalesced into the finest of all Ruby's. And Ruby said, let us make objects, not image nor likeness, for I am a jealous Ruby and I want to be the shiniest Ruby there is. And so was wrought the plainest of all objects. Basic object. And basic object, new kernel and unto them was conceived and born object. And object begot module and module begot class. Now Ruby had given her creations dominion across the land and so object set forth and begot many other classes and multitudes of concrete instances soon spread across the code of the very many Ruby programmers of the world. And so this was the world that all the objects knew and it was great and happy. But Ruby had also furnished her creations with a very special kind of power which was soon to precipitate a great existential crisis. This was the power of introspection and so it was not the first objects doge began to ask what am I and he discovered that he could call the method class and the answer was as plain as day he was a dog. And so the existential crisis passed. Now doge was happy but as would have it dog now had a problem. He asked the same question what am I and he discovered that what he was and he discovered he could also call the method super class and remembered that his parent was none other than object. And so dog knew what he was and where he came from and so he was content. Yet this was not the end soon even the most ancient of objects began questioning their own existence. Basic object class all of them asked what am I and it turned out that all of them were classes and they remembered whom had begot whom. Colonel asked herself the same question and discovered that she was a module and remembered that she had no parent to speak of. And so this was the world that all the objects knew and some of them thought the arrows were getting a little bit messed up because the first wave of the existential crisis was now over but soon enough doge began agonising again he complained to dog you say that as a dog I should be able to bark and wag my tail and be stroked on my belly and so forth I know I'm different from the other dog instances I way different and so on but truly I want more individuality than that not just I want the means doge nature not just to bark but to go so doge and such wow and dog looked at him and shook his head for he knew not what he could do but in the night doge was visited by ruby herself who was full of sympathy for the poor animal and so she spoke thusly I do grant you the power to be the doge that you want to be no longer shall you be a dog but you shall be a singleton yet to keep the peace I cannot make this obvious for if I do dog will be jealous and so was created a new class the singleton class of doge but it was such that if doge called the method class he knew himself still as a dog it was only if he called the method singleton class that he knew where his uniquely doge abilities came from only ruby knew that deeth in the primordial chaos doges was actually his singleton class now doge was happy but of course dog now had a problem he complained to ruby the programmers want me to keep track of all my dog instances and find them by their name I can't do that with normal instance methods I need class methods and ruby remembering what she had granted doge saw a similar solution over here and so she spoke thusly I do grant you the power and not methods that all class objects possess and so ruby created a singleton class of dog and because dog himself was a class this singleton class was like a class of a class and ruby christened it a meta class to distinguish it from the normal singleton classes of ordinary instance objects like doge but in creating the dog singleton class ruby now had to create the class meta class and the module meta class and the object meta class and the basic object meta class and she made it such that the original genealogy was mirrored So it was as if the basic object meta class had begot the object meta class and the object meta class had begot the module meta class an so on and because we like arrows so she made it as if class had begot the basic object meta class So now the objects and the classes they were finally happy but now their world had been very much complicated Dan setiap kali, satu jenis meta-klasik ini adalah untuk membuat kejahatan untuk membuat mereka mempunyai kejahatan mereka. Dan kejahatan meta-klasik ini akan menjadi lebih kejahatan, kejahatan mereka mempunyai kejahatan mereka sendiri. Dan kemudian, akan kejahatan meta-klasik ini berlaku sehingga tiada siapa yang dapat melihat semula perkara. Semua perkara yang biasa dan secara yang biasa hanya mengawal mata mereka dan bergerak dengan hari-hari. Ia mengalami semua kejahatan meta-klasik ini. Dan dengan itu, saya datang ke akhir rupi kreasian rupi saya. Jadi, semoga itu dapat memberikan anda sebuah penjelasan kejahatan kejahatan rupi dalam cara yang menarik. Dan dengan itu, ini adalah masa untuk lebih banyak. Ia adalah masa untuk menjelaskan rupi kreasian rupi dengan kejahatan rupi C-rubi. Jadi, seperti mereka katakan di rupi, semua data dibandingkan sebagai objek. Jadi, doj adalah objek dan dog, yang adalah kejahatan, adalah juga objek. Dan apa yang ini berlaku di rupi c-rubi adalah bahawa semua objek kita dibandingkan sebagai penjelasan kejahatan rupi. Jadi, anda ada penjelasan kejahatan rupi di sini, menjelaskan kejahatan rupi dan kejahatan rupi. Di sini, kejahatan rupi adalah penjelasan, ia adalah sebuah kejahatan rupi. Now, the question of course is, what is actually in this struct? And this is the point where I'm going to show you some actual C-rubi code, just to note this is from the 2.4 branch. So, there will be three structs we are primarily interested in. The first is the struct used to represent ordinary instance objects, our object. Second is the struct used to represent class objects, our class. And finally, you will realise that both of these structs actually store a third struct, our basic. This is the struct that stores information that is basic to all objects. So, first we have a flags member which basically stores metadata of the object. So, whether or not this is an instance object or a class object or a module object, all this is stored in flags. And also whether or not this is a singleton class that is also stored in flags. Secondly and very importantly, we have this class with the K member. So, this is actually a reference to the class of our object. So, it points to an R class struct. And if you look at R object, well, it's just R basic plus stuff. R basic plus this union thing over here which suffice to say is for storing instance variables. And finally, R class. Again, we have R basic. So, we also have flags and class. But importantly, we also have this super member which is a reference to the super class of this class. So, it also points to an R class struct. And then we have this pointer to this something called a Ruby class extension struct which I'm sorry about. But I should mention that since class objects are also instance objects, they also can have their instance variables. And these class instance variables are actually stored in this class extension struct. Finally, we have a pointer to the method table. So, this is where all the instance methods are stored and this is the table that's looked up during method dispatch. So, we talk about method dispatch. This is the core logic right here in this search method function. And I can't find anything. Just go up my inheritance chain, go up my super chain and keep looking up the method tables until we find a method with a matching name. So, if I call the class method on doge, this will be the chain of R class structs whose method tables we'll be looking up. Except, not quite, there's a slight complication here from kernel, which you realize is not a super class yet somehow it manages to find its way into this super chain over here and we'll see more clearly how that happens when we talk about modules later on. So, with that, we have laid the basic groundwork. We've seen how Ruby actually represents our data as pointers to struct. Now, the question is, where does it all begin? Where does our Ruby creation myth actually start? And the answer is in this giant function in object.c called initVMObject it's calling initClassHierarchy. Now, this is the function where our class hierarchy actually gets bootstrapped, right? So, after all these boot def class function calls, our hierarchy grows like this and all these are basic set class macro calls are basically setting the class pointer of basic object object, module and class to point to class. And at this point, maybe you're like, where is kernel? Well, if you go back to initVMObject, which is a really gigantic function, and there, we are initializing kernel and including it in object. And so, at the end of all this, your diagram looks something like this. Pretty much what we saw from the creation myth just now. In addition to basic object, object, module and class and kernel, Ruby also initializes all the built-in classes such as nil, string, array, true class and so forth on initVMObject. So, we're happy with that. But now, the next interesting question is, what happens when I actually define my own class? So, say I want to define a class dog. A key function that gets called is RB define class ID. And this basically does three things. The first is, if we don't specify an explicit super, we set the default super to object. You all know, right? Second, we actually create the new class, so we initialize a new R-class struct with the given super. And this also sets the class pointer of this R-class struct, 2.2 class with the capital C. And finally, we actually make and set the meta-class of this class straight away. So, before we talk about meta-classes and all, our diagram looks like this. It's pretty simple, right? We just created a new class dog, we set it as super, we set it as class pointer. Okay, but this is before meta-classes. And before I want to talk about meta-class creation, I guess I should clarify the terminology because I guess this is always a point of confusion for beginners and as it was for me, basically a single-ten class is synonymous with eigen-class. And a meta-class is a kind of single-ten class, but specifically it is a single-ten class of a class object. So we speak of the meta-class of dog but the single-ten class of doge. And since meta-classes are also classes, you can have meta-classes or meta-classes. So meta-meta-classes, meta-meta-meta-classes, in general meta to the end-classes. Although you'll be like, well, pretty much any end greater than one is practically useless. But it's pretty fun to know that Ruby actually allows you to do this. And so with that, let's talk about meta-class creation. Boromir knows well that one does not simply make a meta-class. And indeed, if you look at the make meta-class function, it is not easy to digest. But again, it does basically three things. First, we actually initialize our new meta-class. So we initialize the new R-class struct. Second, we set the class pointer of our class. So the class pointer of dog. We set the class pointer of that to point to our new meta-class. But we also have to set the class pointer of our new meta-class to point to something. And finally, we set the super pointer of our new meta-class to point to, again, something. Oh, we actually start setting the class pointer of our new meta-class. Our diagram looks like this. Again, it looks pretty simple. We have just created a new meta-class of dog. But you notice that that class pointer it has to go somewhere. So how do we set the class pointer? If you look at the logic of the function, you notice that we end up calling this funny and sure-agon class macro. So what this macro does is return the meta-class of class. So in other words, the super-class of dog's meta-class is class' meta-class. And if class' meta-class hasn't already been created, we have to make it, right? So let's go and make the meta-class of class. Then, same thing. We initialise the meta-class of class. And now we meet the same problem. We have to set the class pointer of the meta-class of class. But how do we resolve it in this case? The class pointer just points back to itself. And so our diagram looks something like this. So we're happy if the class pointer, but now you notice you have another problem. What about the super? Well, if you look at how the super is set, you realise again we are calling this ensure-agon class macro. And the logic of this line is that the super-class of dog's meta-class is the meta-class of dog's super-class. Or in other words, the meta-class of module. So you see that we're setting off this whole chain reaction. We have to make the meta-class of module. And now we have to set the super of the meta-class of module. So by the same logic, the super of the meta-class of module should be the meta-class of object. And the super of the meta-class of object should be the meta-class of basic object. And now we have reached the end of the chain. So what should the super of basic... the meta-class of basic object be? Well, it turns out that it's just class. So it turns out that we actually haven't finished creating our dog meta-class. We still have to set its super-pointer. So finally, after creating all those meta-classes, we can come back and set the super-pointer of our dog meta-class. And logic is the same, right? The super-class of dog's meta-class is the meta-class of dog's super-class. And so it's the meta-class of object. So congratulations, we are done. All we wanted to do was create a single dog-class but we ended up spawning and indeed Boromir was right. And maybe at this point, you're like, ah, all this meta-class stuff is insane. But fret not, because compared to that, making normal singleton-classes is the proverbial walk in the park. And it is evidenced by how short this function is. So, again, it does basically three things. First, we actually initialise the R-class struct, the new singleton-class. And importantly, we set the super of this new singleton-class to point to the original class of our instance object. The original class of doge was dog so we set the super of doge's singleton-class to point to dog. And then we actually set the class pointer of doge to point to this new singleton-class. And finally, we set the class pointer of our new singleton-class to point to the meta-class of class. And so at the end of all this, you have something like that. It's pretty much where our creation is left off. So at this point, you will realise that we are still missing part of Ruby's object model, which is modules. So how does including modules actually work? And the answer is that Ruby finds a pretty clever way of sneaking modules into our inheritance chain with something called include classes. So let's take a look at how that works. I want to give doge some sabertief. So I include the sabertief module in the dog class. What happens when I do this is that Ruby creates a special pattern of class called an include class and inserts it to our inheritance chain just like so. And the special thing about this include class is that it actually shares the same method table as our module. And in this way, method dispatch just works like normal. We just go up the inheritance chain. So this is where the bulk of the module inclusion logic can be found. This include modules add function, which, honestly speaking, is a really hefty function. I'm not even sure if I completely understand it at this point yet. But it's a key part where we're actually creating our new include class and inserting it into the inheritance chain with this r-class set super macro. And I should mention that since include classes and modules, they are just r-class structs, right, internally. And r-class structs, they have a class pointer. So what should a class pointer be set to? Well, for modules, we know the class pointer is set to module with the capital M. So the class of the sabertief module is module. But it turns out that the class sabertief include class, as you can see here, is actually the module itself. And so the class pointer is how our include class keeps track of the module it was created from. So if you include multiple modules in a class, so instead of just sabertief, I want to give Doge some sunglasses, because it's cool, right? So if I do that, because the include module set function is called every time include statements evaluated, by the logic of how the include class is inserted into the inheritance chain, the module that's included later basically has its include class inserted lower down the chain. Or in other words, we look up its method table first. So let us now complicate things a bit. Let us talk about including modules in modules. Now, at this point, I should emphasize again that modules and include classes are just r-class structs. And other than the class pointer, r-class structs also have a super pointer, right? And maybe at this point, you're like, but can modules have a super? And the answer is yes, but not by default. By default, the super is just now, but the super comes in handy when we want to include modules in modules. So let's take a look at how that works. Instead of giving Doge sabertief and sunglasses, I want to give him a pair of sabertief sunglasses. How cool is that? Right? So first, I include the sabertief module in the sunglasses module, and then I finally include the sunglasses module in my doc class. What happens when I include a module in a module is really not too different from what happens when I include a module in a class. Basically, I create an include class of the included module, and I set the super pointer of the module I'm including that module in to point to the include class, right? So now the super pointer of the sunglasses module points to the sabertief include class. And finally, when I actually include the sunglasses module in doc, I just get two include classes inserted into the inheritance chain just like this. So awesome, I've covered a lot of ground. I've talked about how classes and objects are actually represented in Ruby. How singleton classes and meta classes are actually created. And how modules actually work. How including modules actually work and extending modules isn't much more than either. So before I wrap up, I would be remiss not to mention just to clarify the distinction between the C class and super pointers and the Ruby class and super class methods. So why do they seem to give different results? Well, it's pretty simply that Ruby's class method it does follow the class pointer in the C struct but it just ignores singleton classes and include classes along the way. And so the class of the doc singleton class is still class, right? And Ruby's super class method it also follows the super pointer of the C struct but it just ignores include classes along the way. And so even though the super of our doc class is active include class it's super class is still object. So maybe at this point you're like ah, yeah, all this under the hood internal stuff it is fun to know but why? It's not going to make me a better Ruby programmer in my day to day or anything, right? And yeah, I guess to a certain extent I agree and I know it's not particularly helpful to know that I can create meta, meta, meta meta, meta, meta, meta classes. But I'd like to think that having a solid understanding of Ruby's object model does help you reason about your code better, more clearly. And if in the future you ever meet potentially confusing situation such as all of this, I mean there's no need for me to even talk about it not just because I'm running out of time but mainly mostly because you now have the mental model to figure out all by yourself the mental model that I wish I had starting out and more So, thank you Okay Any questions? Don't lie No one has questions? Oh, okay, yeah, go ahead Oh, hi First off, that was amazing That was an amazing presentation My question is how did you learn all this stuff? Did you just read through the C files? Well, yeah, so I think around last year I saw this book, Ruby Under the Microscope and actually I looked through the first few pages and I was like, ah, why am I reading this and put it aside and after a while I decided maybe I should it's fun to know about this internal stuff so I decided to poke around the C-Ruby resource but of course as a beginner how do I understand all of this and then I found this really good resource by Aoki Minoru It's actually for Ruby 1.8 but, I mean, the internals the core of Ruby hasn't really changed or that much, mainly is the VM they've written a new VM so it was still really helpful for me in understanding how the C-Ruby code is laid out so if you're interested in checking it out definitely look out for the Ruby hacking guide Thank you So, Aaron kind of stole half of my question but so when you started out researching this that far down the rabbit hole or did you actually set out to find out all of that all the way or did it just happen and you started looking and it was like damn, where does that lead and it led to all of this So the initial ambition was let me just try to give a talk at RDRC what topic can I talk about and as a beginner, I'm not even a professional so I can't talk about my project I don't really have projects to talk about so it kind of just like let me try that because the Ruby's Object Model is pretty fun when I realised that there's this whole infinite meta-class thing going on I was like, let me poke more into that and then like you said, I just poked into it poked around and it just ended up wow, this is all the stuff that goes on and just escalated quickly cool, thank you once you learned all this stuff did it change the way that you now write Ruby code understanding it, did you I don't know, start using modules more often did you start extending included modules etc I mean, definitely I guess I will understand a lot of meta-programming stuff better now because I mean, a lot of people use like I don't know, like class method and then they have a self-taught included hook and I'm like, what's all this do so definitely that helps me understand confusing meta-programming stuff but you mean confession is that I haven't actually done Ruby because I don't actually use it for work or in school how did you find out about the meta-meta-meta stuff did you read that in some book or in the C code or did you use like puts and over-road like inherited or something like that actually it's in Ruby docs itself so if you look at the class documentation with the class hierarchy and then there's a chain of dashes off into infinity so that's when I knew something sneaky I saw that years ago and I never understood what does it mean just dot dot dot okay, thank you any other question no okay, thank you very much let's have another round of applause