Lua

The LuaJIT Wiki

not logged in | [Login]

Parameterized Types

Parameterized types is a feature of the LuaJIT FFI. It enables one to specify FFI cdefs dynamically. It is useful for generic programming, similar to why one might use C++ templates or Java generics.

This feature was introduced in June 2012 and is not available in any release (as of beta10); you must clone the git repo to use them.

To use this feature, you can place the $ character in a C type declaration, used by ffi.cdef and ffi.type. When you use this declaration, you pass arguments along with it. These arguments can be a cdata or ctype or number.

Here are some examples (which in isolation are not very interesting):

ffi.typeof("$ *", foo_t)
ffi.typeof("struct { $ k; $ v; }[?]", foo_t, bar_t)
ffi.typeof("uint8_t[$][$]", width, height)

Documentation from mailing list

The following was posted on the LuaJIT mailing list.

The $ character is the marker to be replaced with parameters in a C type declaration:

  • A ctype or a cdata parameter is interpreted like an anonymous typedef. You can use that to construct derived types, e.g. ffi.typeof("$*", ct) is a pointer to ct, or "$[10]" is a 10 element array. Unlike simplistic string concatenation, this works for all cases (e.g. pointers to function pointers).

  • A string parameter is treated like an identifier or keyword, except the string is not parsed again or split into words (this isn't simple textual substitution). You can use that for field names, function names, argument names etc.

    [I'm pondering whether I should drop the keyword and type lookup on the string. That would allow you to use arbitrary names for fields, even when they collide with keywords or typedefs. I may change that before writing the docs.]

  • A number parameter is treated as an integer. You can use that to construct fixed size arrays. In some contexts you may prefer this over VLAs. It works for multi-dimensional types, too. E.g.:

  local matrix_t = ffi.typeof("uint8_t[$][$]", width, height)
  ...
  local m = matrix_t()

Only ffi.typeof() and ffi.cdef() parse parameterized types. The other functions, e.g. ffi.new(), don't do that, because they already take other arguments (e.g. initializers). I guess it would be too confusing to mix two kinds of arguments.

Anyway, parameterized types are a nice tool, but you'll want to use it sparingly!

The typical scenarios where you'd use templates in C++ come to mind: e.g. construct a stack or a queue of an arbitrary type. These need to create an array of the element type, which is now really easy to do. [Justin's use case]

Another example are derived types of anonymous structs. This avoids pollution of the struct namespace. [Henk's use case]

Stack Example

Mike posted this example on the LuaJIT mailing list:

local ffi = require("ffi")

local function stack_iter(stack)
  local top = stack.top
  if top > 0 then
    stack.top = top-1
    return stack.slot[top-1]
  end
end

local stack_mt = {
  __new = function(tp, max)
    return ffi.new(tp, max, 0, max)
  end,
  __index = {
    push = function(stack, val)
      local top = stack.top
      if top >= stack.max then error("stack overflow") end
      stack.top = top + 1
      stack.slot[top] = val
    end,
    pop = function(stack)
      local top = stack.top
      if top <= 0 then error("stack underflow") end
      stack.top = top-1
      return stack.slot[top-1]
    end,
    iter = function(stack)
      return stack_iter, stack
    end,
  }
}

local function makestack(ct)
  local tp = ffi.typeof("struct { int top, max; $ slot[?]; }", ct)
  return ffi.metatype(tp, stack_mt)
end

local stack_t = makestack(ffi.typeof("double"))

local stack = stack_t(100)
for i=1,100 do stack:push(i) end
for x in stack:iter() do io.write(x, " ") end
io.write("\n")

Libraries

The following libraries use Parameterized Types: