
Everything You Didn't Want to Know About Lua's Multi-Values - benaiah
https://benaiah.me/posts/everything-you-didnt-want-to-know-about-lua-multivals/
======
jmaa
I do not really agree with the article's (implied?) statement that multivalues
are an ugly part of Lua. It's subtle yes, but like most of Lua, is incredibly
consistent.

For example, let's compare with Python: In Python, you can return multiple
arguments as a tuple; it's not possible to have an optional return value
without a lot of painful type matching and unpacking. In Lua you can:

    
    
      local result, errmsg  =  do_stuff()
      if not result then
        error("Error in do_stuff:"..errmsg)
      end
      print(result + 5)
    

If you don't want the error message, just remove ", errmsg", and it will work
as expected. This is incredibly flexible, which is exactly what I want in my
dynamic languages. In Python, you would get an type error when attempting to
add together a tuple and an integer.

~~~
saagarjha
But why not just use a table in this case? Indexing a value that doesn't exist
will give you nil as well.

~~~
wahern
Tables require dynamic allocation--as do Python tuples--which cause GC churn
even when optimized. And accessing a table performs a lookup operation just to
put it onto the value stack, anyhow. While insertion and index operations are
fast, they're nowhere near as fast as not doing them at all. If you want stack
semantics, just use the stack! Multiple return values (return lists) are one
reason why Lua is often faster than Python and JavaScript in an apple-to-
apples (i.e. comparing ad hoc application logic that doesn't rely on Python's
hundreds of megabytes of C modules, no JIT (though return lists make JIT'ing
easier), etc). The article says that they "don't perform notably better", but
in my 15 years of experience with Lua that's just absolutely not true,
especially on hot paths.

But return lists are more than an optimization. Lua is a first-class
functional language with guaranteed tail-call optimization. And Lua maintains
strong semantic symmetry between its C API and in-language constructs. In both
cases return lists provide an extremely elegant way for composing function
calls, including composing mixtures of Lua and Lua C functions.

For example, returning a list in C is as simple as `push; push; return 2`,
whereas in languages that require an array or tuple you need to need to create
a separate object and then insert your value (by index or name). It's worth
noting that Lua can handle allocation failure gracefully, throwing an error
back to the more recent protected call while maintaining a clean state (e.g.,
OOM from an event loop client handler won't crash your app and the hundreds of
thousands of other connections). Many simple functions that operate on the
stack (e.g. lua_pushinteger) are guaranteed not to dynamically allocate. Such
guarantees can make writing OOM-safe Lua C API code _much_ easier.

If you look at it from the perspective of the C FFI, return lists make a _ton_
of sense. Indeed, they're integral to the C API semantics, which is defined in
terms of an abstract invocation stack--which is in fact implemented as a
contiguous array.

~~~
saagarjha
This explanation went far more in depth than I expected it to. Thanks for
writing it out!

