As often in Lua, there technically is a subtle difference. But that difference most probably won't matter to you at all. I generally prefer the OOP-style for calling string methods, since it's more concise and doesn't require string
to be in scope.
If myWord
is guaranteed to be a string and the string metatable has not been tampered with, these are equivalent and the latter is just syntactic sugar for the former.
As for the nitty-gritty details:
string.format(myWord, "World")
looks string
up in _ENV
(typically _G
, the global table), then accesses the format
field of that, then calls that with myWord
and "World"
as arguments.
myWord:format("World")
indexes myWord
, then it accesses the format
field and calls that with arguments myWord, "World"
.
If myWord
is a string, indexing myWord
will hit the metatable, which has the string
table set as __index
, unless someone did getmetatable"".__index = {}
or similar tampering with the string metatable.
(An oversight that happens sometimes in Lua sandboxes is to forget to make the string metatable inaccessible.)
Perhaps the most relevant subtle difference in behavior is that string.func(s, ...)
will coerce s
to a string if it is a number, whereas s:func(...)
will throw an "attempt to index a number value".
This may be relevant if you're maintaining a legacy Lua API and "refactoring" it to use the latter style, accidentally breaking the code of API users who relied on the implicit number to string coercion.
Another maybe-relevant difference is that s:func(...)
would let you supply a table s
with a metatable set such that s:func(...)
does something sensible: It plays better with polymorphism. For example you could have a GraphemeString
"class" which supports (its own version of) s:sub
, but operates on graphemes rather than bytes. If you're using the "OOP" style, you could easily swap one for the other. (If you're using the "imperative" style, you could still monkey-patch string.format
etc., but that would be messy.)