Luau is often referred to as an OOP language, even though it has no built-in classes.
What are prototypes and prototype programming (PP)?
How are familiar Object-oriented concepts emulated based on them?
- In classical languages (C++, Java, C#), there is a strict division into classes and instances, and inheritance is based on a strict hierarchy.
- In prototype—oriented languages (like Luau), instead of classes, there are objects (tables) that can act as prototype templates for other objects.
When trying to get a field from an object that it does not have, Luau automatically accesses its prototype through the index meta method in the meta table. Due to this, the object gets “inherited” methods and properties:
That is why Luau is considered an OOP language: all the basic principles (encapsulation, inheritance, polymorphism) are implemented through tables and meta-tables, and not through real classes.
I’ll say “prototype” instead of “class”
What is prototype and prototypal programming?
The prototype is a regular Lua table which just contains the keys. In the context of the luau OOP, these are usually functions. There is no class keyword in Luau, the prototype is just a table with a set of functions (methods) and maybe a constructor.
When creating an “instance” of a prototype, setmetatable(newObj, proto)
is usually used, where proto.__index = proto
. Due to this, if newObj does not have the required field or method, Luau searches for it in the proto table (in its __index
) and returns it from there.
Prototypal programming (or prototypal OOP) is a style in which there is no division into classes and instances: any object can act as a template (prototype) for others.
Inheritance occurs through a reference to the prototype, not through the “extends” declaration.
In such languages, this is exactly how OOP works.: objects inherit from each other through a chain of meta tables (or the Prototype property in JS)
Why is “prototype” → not “class”?
- There is no class keyword and no built-in class system in Luau. There are only tables and meta-tables.
- Any table can be used both as a “class” and as an “instance”. The only difference is where it links to via the __index.
- If object A serves as a prototype for objects B, C, etc., then it is not a class in itself, but just a “template” table
Classes via (meta) tables
To emulate the familiar “class–instances” pattern, Luau usually does this:
Option 1 (easy)
Table — some keys are functions, some keys are other values. This is exactly the “archetype” of the facility in Luau.
this is a bad way because it takes up a lot of memory. I’ll probably explain why below.
Option 2:
- Create a table. Let’s call it “proto”, add functions (methods) to it, which should be common to all “instances”.
- Set proto.__index = proto so that if the X field is missing from the Luau object, it searches for proto.X .
- Write the constructor function proto.new(), which creates an empty table (a new object) and calls setmetatable(newObj, proto). As a result, the new object will “inherit” all the methods from proto.
it is important for a beginner to understand that
proto.__index = proto is just a convenient “trick” that allows you not to write setmetatable(newObj, { __index = proto }) in the constructor every time. But this technique creates an extra field in the prototype table, which I personally (I’m not the only one) consider ugly, so I don’t do that.
Sometimes this can be justified:
For example, in the register
BaseZombie
-ZombieType
Where BaseZombie returns the createZombie function and not the prototype, we don’t seem to be able to find out the baseZombie prototype in any way, but no — we have baseZombie.__index
This is a real-life example. Maybe you’ll have to use it sometime. Besides, my example is not very good, and later you will understand why.
Prototype vs Class
1 Lack of class hierarchy:
In classical languages, we declare class A, class B extends A, class C extends B, and so on. There is no extends keyword in Luau — instead, “inheritance” is implemented through the __index chain: meta tables can link to other tables, forming branching or even “multiple inheritance” in the spirit of mixins. However, it is inconvenient to maintain a “clean” multiple hierarchy: you always have to manually merge fields from different prototypes. Multiple inheritance is more often avoided by choosing mixins or composition.
- Delegation:
In the classics, an object calls a method that is initially searched for in its own class, then rises through the hierarchy. In the prototypal approach, if an object does not have a foo field, Luau takesobject.__index = proto
, and proto can have its own meta—table, etc. - a chain of prototypes. This is called delegation.
cons, limitations
- Lack of encapsulation:
Classes usually have private, protected, and public. In Luau, everything is open, unless you use tricks like closed upvalues or __metatable locks. But in general, any script, having a reference to an object, can read or modify any of its fields. This is compounded when usingproto.__index =proto
, when prototype methods and properties are “mixed up”. - Lack of internal typing — and there is nothing to supplement
It can also be implemented in a different way, as I have already mentioned. The table with the method and function is already not only a prototype, but also an object.
local function new(self)
local function other()
return self.N + 1 — we use self here
end
return {Data, other} -- its also object, but here no prototype!
end
This is a bad practice, because every time it declares a function, it takes up a lot of memory, this is called “closures”. In the understanding of many people, meta-tables (namely, the index meta-method) were created precisely in order to get rid of closures, i.e. creating the meta-table itself for an object is slower than declaring functions, but in the long run it is better.
As a result, instead of trying to recreate a complex class–heir–heir hierarchy, Luau often creates Entity-Component (ECS)-like architectures — and this scales well, especially for game projects.
Conclusion
:
there is an OOP in luau, which is more correctly called POP → prototype-oriented programming.
Any feetback is welcome