How do you create a Lua object that only exposes its attributes and not its methods? For example:
local obj = {
attr1 = 1,
attr2 = 2,
print = function(...)
print("obj print: ", ...)
end,
}
Produces:
> for k,v in pairs(obj) do print(k, v) end
attr1 1
attr2 2
print function: 0x7ffe1240a310
Also, is it possible to not use the colon syntax for OOP in Lua? I don't need inheritance, polymorphism, only encapsulation and privacy.
I started out with the above question and after chasing down the rabbit hole, I was surprised by the limited number of examples, lack of examples for the various metamethods (i.e.
__ipairs
,__pairs
,__len
), and how few Lua 5.2 resources there were on the subject.Lua can do OOP, but IMO the way that OOP is prescribed is a disservice to the language and community (i.e. in such a way as to support polymorphism, multiple inheritance, etc). There are very few reasons to use most of Lua's OOP features for most problems. It doesn't necessarily mean there's a fork in the road either (e.g. in order to support polymorphism there's nothing that says you have to use the colon syntax - you can fold the literature's described techniques in to the closure-based OOP method).
I appreciate that there are lots of ways to do OOP in Lua, but it's irritating to have there be different syntax for object attributes versus object methods (e.g.
obj.attr1
vsobj:getAttr()
vsobj.method()
vsobj:method()
). I want a single, unified API to communicate internally and externally. To that end, PiL 16.4's section on Privacy is a fantastic start, but it's an incomplete example that I hope to remedy with this answer.The following example code:
MyObject = {}
and saves the object constructor asMyObject.new()
setmetatable()
and__metatable
)__newindex
)__index
)__index
)__pairs
,__len
,__ipairs
)__tostring
)Lua 5.2
Here's the code to construct a new
MyObject
(this could be a standalone function, it doesn't need to be stored in theMyObject
table - there is absolutely nothing that tiesobj
once its created back toMyObject.new()
, this is only done for familiarity and out of convention):And now the usage:
Which produces the expected - and poorly formatted - output:
.
to access either an attribute or a method)This style does consume slightly more memory per object, but for most situations this isn't a concern. Factoring out the metatable for reuse would address this, though the example code above doesn't.
A final thought. Lua OOP is actually very nice once you dismiss most of the examples in the literature. I'm not saying the literature is bad, btw (that couldn't be further from the truth!), but the set of sample examples in PiL and other online resources lead you to using only the colon syntax (i.e. the first argument to all functions is
self
instead of using aclosure
orupvalue
to retain a reference toself
).Hopefully this is a useful, more complete example.
Update (2013-10-08): There is one notable drawback to the closure-based OOP style detailed above (I still think the style is worth the overhead, but I digress): each instance must have its own closure. While this is obvious in the above lua version, this becomes slightly problematic when dealing with things on the C-side.
Assume we're talking about the above closure style from the C-side from here on out. The common case on the C side is to create a
userdata
vialua_newuserdata()
object and attach a metatable to theuserdata
vialua_setmetatable()
. On face value this doesn't appear like a problem until you realize that methods in your metatable require an upvalue of the userdata.Note how the table created with
lua_createtable()
didn't get associated with a metatable name the same as if you would have registered the metatable withluaL_getmetatable()
? This is 100% a-okay because these values are completely inaccessible outside of the closure, but it does mean thatluaL_getmetatable()
can't be used to look up a particularuserdata
's type. Similarly, this also means thatluaL_checkudata()
andluaL_testudata()
are also off limits.The bottom line is that upvalues (such as
userdata
above) are associated with function calls (e.g.LI_MyType__tostring
) and are not associated with theuserdata
itself. As of now, I'm not aware of a way in which you can associate an upvalue with a value such that it becomes possible to share a metatable across instances.UPDATE (2013-10-14) I'm including a small example below that uses a registered metatable (
luaL_newmetatable()
) and alsolua_setuservalue()
/lua_getuservalue()
for auserdata
's "attributes and methods". Also adding random comments that have been the source of bugs/hotness that I've had to hunt down in the past. Also threw in a C++11 trick to help with__index
.The lua script side of things looks something like: