alex.lgbt

Why I Think Lua is Great

10:52am on 20th June 2020 correct a mistake

Recently, I discovered how easy embedding Lua scripts into production applications is. At the time I believed that it was terrible, purely due to its arrays — they’re indexed from 1, not 0 — and its use cases (looking at you, ROBLOX).

I had the idea of designing an API for an image processor; this would be really tricky to implement in a REST API. Obviously GraphQL wouldn’t help much, so the only other option I could think of was opening a sandboxed V8 JavaScript runtime or something similar to the public. This seemed to be really hard to implement well, especially in Go.

I surfed GitHub for a while and decided to use gopher-lua. It appealed to me because — while having a smooth learning curve — performed relatively well. According to its benchmarks, it performs at around the speed of Python 3. You might not think that this is impressive, but considering it has really easy-to-use bindings to Go, etc, I think it’s great. Shopify also made a similar library; it doesn’t perform as well, however.

In about half an hour, I wrote LuAPI. To be fair, it’s only around 150 lines of code, but it’s exceedingly powerful. Essentially, it lets clients POST to a single endpoint with a Lua script. This script is able to fully interact with Go functions defined by the developer; gopher-lua’s integration is pretty neat.

Here’s an example of defining a Lua function that calls Go:

l.SetGlobal("go", l.NewFunction(func(state *lua.LState) int {
	state.Push(lua.LString("Hello from Go!"))
	return 1
}))

We can then use DoString to run a script:

script := `
function main()
    return go()
end

print(main())
`
if err := l.DoString(script); err != nil {
    panic(err)
}

Yes, that script is purposefully over-done; I simply wanted to demonstrate that it works.

This prints “Hello from Go!” to the console. It’s simple, but it can easily be extended to work with an API. You can see LuAPI here:

Go wasn’t the only language that I wanted to use with Lua. I discovered lupa, a Python library; it has even easier ways to interface cross-language. Just for a test, I decided to write something that lets Discord users write their own contained Lua scripts for their servers. This used discord.py; I was simply able to pass the guild object to Lua as if it was a Python function.

def run_script(self, script: str):
    sandbox = self.runtime.eval(SANDBOX_SCRIPT)
    return sandbox(self.guild).run(script)

SANDBOX_SCRIPT refers to a Lua function that returns a closure. Lupa converts this into something we can use directly from Python, as can be seen; we call sandbox, passing the guild, then use the returned value’s run method. Note that I defined said method in Lua. It’s not a Lupa built-in. The return value of that function is, well, returned in Python too; I was easily able to expose a fully-working sandbox:

server = Guild(message.guild)
try:
    res = server.run_script(message.content)
    await message.channel.send(res or "No content")
except Exception as e:
    await message.channel.send(e)

This is why I think Lua is amazingly powerful. It’s just so easy to create production-ready, secure yet fast and scriptable applications. Oh, and almost anyone can learn it. This doesn’t, however, mean that I want it to be used in the creation of applications themselves. I just think it’s a great option for integrating scripting.


Tags: programming, lua, go, python

Written by Alex P
I'm just a Rust and Go developer from the UK. GitHub

Star me on GitHub!
Licensed under CC-BY-SA unless otherwise stated.