PrevUpHomeNext

class lua_ref

A primer::lua_ref is a reference to a lua value which exists in a lua VM.

More precisely, it is an RAII object which manages a lua reference created by luaL_ref and cleaned up by luaL_unref (see documentation).

primer::lua_ref doesn't have the semantics of a C++ reference. It has an empty state, and can be assigned and reassigned to point to different objects. Effectively, it's more like a smart pointer which points to some lua value.

Basic Usage

To bind a lua_ref to a lua value,

  1. Push the value onto the top of the stack.
  2. Construct a lua_ref from the pointer lua_State * for that stack. This pops the object from the stack.

If the stack is empty, then the lua_ref is placed in the empty state.

It can be default constructed in the empty state as well.

The value can be obtained from the reference by using the push methods, or the as method. The as method will first push the value to the stack, then try to primer::read it, then clean up the stack.

If the lua VM is destroyed (closed), the lua_ref reverts to the empty state.

lua_State * L = luaL_newstate();

std::string foo{"foo"};
primer::push(L, foo);
assert(lua_gettop(L) == 1);

primer::lua_ref ref{L};
assert(lua_gettop(L) == 0);
assert(ref);

assert(ref.as<std::string>() && foo == *ref.as<std::string>());
assert(!ref.as<int>());
assert(!ref.as<bool>());
assert(ref.as<primer::truthy>() && true == ref.as<primer::truthy>()->value);

assert(ref.push());
assert(lua_gettop(L) == 1);
assert(lua_isstring(L, 1));
assert(foo == lua_tostring(L, 1));

lua_pop(L, 1);

assert(ref);
assert(ref.as<std::string>() && foo == *ref.as<std::string>());

lua_close(L);

assert(!ref);
assert(!ref.as<std::string>());
Synopsis

There are two components to the lua_ref object:

class lua_ref {
  1lua_state_ref sref_;
  2mutable int iref_ = LUA_NOREF;

public:
  // Special member functions
  lua_ref() noexcept = default;
  lua_ref(lua_ref && other) noexcept;
  3lua_ref(const lua_ref & other);
  ~lua_ref() noexcept;

  lua_ref & operator=(const lua_ref & other);
  lua_ref & operator=(lua_ref && other) noexcept;

  // Primary constructor
  4explicit lua_ref(lua_State * L);

  // Reset to empty state
  5void reset() noexcept;

  // Standard swap function
  void swap(lua_ref & other) noexcept;

  // Attempt to obtain a pointer to the underlying lua VM.
  // Succeeds if it wasn't destroyed yet.
  // Always returns a pointer to the *main thread* stack.
  6lua_State * lock() const noexcept;

  // Push to the main stack
  // Return value is same as lock()
  7lua_State * push() const noexcept;

  // Push to a thread stack (see "coroutines" in the manual)
  8bool push(lua_State * T) const noexcept;

  // test validity
  9explicit operator bool() const noexcept;

  // Try to interpret the value as a specific C++ type
  10template <typename T>
  expected<T> as() const noexcept;
};

1

A weak reference to a lua state. See <primer/support/lua_state_ref.hpp> for details.

2

Holds the registry index to the object. Mutable because, if sref_ becomes empty, we want to set iref_ to LUA_NOREF immediately.

3

If lua can't allocate memory for the copy, throws std::bad_alloc.

4

Pops an object from the top of given stack, and binds to it. If no object is on top, enters the empty state.

Note: This can cause a lua memory allocation failure.

5

Releases the lua reference, reverts to empty state.

6

Returns a valid lua_State * if successfully locked. Nullptr if not. No-fail.

7

Note: Does not check for stack space

This cannot cause lua memory allocation error.

8

Attempts to push the object onto the top of a given thread stack. It must be a thread in the same VM as the original stack, or the same as the original stack.

Returns true if push was successful.

Returns false and pushes nil to the given stack if the original VM is gone.

[Caution] Caution

If you try to push onto a stack belonging to a different lua VM, undefined and unspecified behavior will result.

If PRIMER_DEBUG is defined, then primer will check for this and call std::abort if an error was made.

If PRIMER_DEBUG is not defined... very bad things are likely to happen, including stack corruption of lua VMs.

9

Test if we can still be locked.

10

Attempt to cast the lua value to a C++ value, using primer::read

This cannot cause an exception or raise a lua error. It performs a stack space check.

Safety and ownership
[Caution] Caution

Copying a lua_ref is expensive relative to most other small objects and usually something that you want to avoid, since it involves interacting with the lua VM.

When running without PRIMER_NO_MEMORY_FAILURE, it further requires a protected context to catch a lua memory allocation failure.

In low memory scenarios, this should likely be avoided. For instance, a shared_ptr<lua_ref> is just as useful as a lua_ref and can be copied without making new dynamic allocations. If you are trying to improve performance you might consider profiling something like that in your application.

In general prefer to move these whenever possible.

[Caution] Caution

You must not pass these objects across operating-system threads. Lua is generally not thread-safe anyways, so this should come as no surprise.

Read / Push semantics

Using primer::push with a primer::lua_ref calls the thread-push member function, and so comes with all the caveats above -- if you push it to an unrelated lua VM, it won't work and bad things will happen.

Using primer::read with a primer::lua_ref always succeeds in binding to a value at the given position, unless it is nil or out of bounds, in which case it produces a lua_ref in the empty state.

This makes it effectively an "any" type for purposes of function parameters, and you can use lua_ref::as to easily try to interpret it as other values later. So it can be used sort of as a poor man's variant.

Here's an example function to illustrate:

primer::result
ref_test_func(lua_State * L, primer::lua_ref ref) {
  if (!ref) {
    lua_pushstring(L, "nil");
    return 1;
  } else if (ref.as<bool>()) {
    lua_pushstring(L, "bool");
    return 1;
  } else if (ref.as<int>()) {
    lua_pushstring(L, "int");
    return 1;
  } else if (ref.as<std::string>()) {
    primer::push(L, ref);
    return 1;
  }
  return primer::error("I can't handle it!");
}

And example semantics:

lua_CFunction f = PRIMER_ADAPT(&ref_test_func);
lua_pushcfunction(L, f);
lua_setglobal(L, "ref_test_func");

const char * script =
  ""
  "assert(ref_test_func() == 'nil')           \n"
  "assert(ref_test_func(5) == 'int')          \n"
  "assert(ref_test_func('foo') == 'foo')      \n"
  "assert(ref_test_func(true) == 'bool')      \n"
  "assert(not pcall(ref_test_func, 5.5))      \n"
  "assert(not pcall(ref_test_func, {}))       \n"
  "assert(pcall(ref_test_func, nil))          \n";

assert(LUA_OK == luaL_loadstring(L, script));
assert(LUA_OK == lua_pcall(L, 0, 0, 0));

Note that, it is not too difficult to implement your own read and push specializations for a proper variant type that appears in your program. We don't currently have a generic boost::variant header in Primer, so you would have to make your own.


PrevUpHomeNext