Last active
April 12, 2026 13:51
-
-
Save VonHeikemen/45c970f16cc8059bba7af4fafb423975 to your computer and use it in GitHub Desktop.
Coordinate tasks using lua coroutines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local M = {} | |
| local function pack_len(...) | |
| return {n = select('#', ...), ...} | |
| end | |
| function M.run(f) | |
| local t = coroutine.create(f) | |
| local context = {} | |
| context.resume = function(...) | |
| if coroutine.status(t) ~= 'suspended' then | |
| return | |
| end | |
| local ok, e = coroutine.resume(t, ...) | |
| if not ok then | |
| error(e) | |
| end | |
| end | |
| context.yield = function() | |
| return coroutine.yield() | |
| end | |
| context.await = function(f, ...) | |
| local args = pack_len(...) | |
| local argc = args.n + 1 | |
| args[argc] = context.resume | |
| f(unpack(args, 1, argc)) | |
| return coroutine.yield() | |
| end | |
| local ok, e = coroutine.resume(t, context) | |
| if not ok then | |
| error(e) | |
| end | |
| end | |
| return M |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example
This is about coordinating functions. I'll be using a Neovim functions as example.
In Neovim
vim.ui.input()is like an interface that people extend (with something like snacks.nvim input), and when they do is usually non-blocking. This is great because you don't freeze the editor while trying to type something on the input. The downside is that we have to use callbacks.The problem is
namewill not be available outside the callback. As a user you are happy that the editor is not frozen. But as the author of the code is frustrating that you can't just write "normal code" that reads like a sequence of steps.With coroutines we can pause and resume a function. Long story short, with
coroutine.yield()we relinquish control to the "top level scope" that is executing our function. We are pausing the function. Waiting.coroutine.resume()gives the control back to us. If you make a few tweaks a here or there you can flatten the stack and make it easy to use.Provided that the callback is the last argument of the non-blocking function we could use
ctx.await().This eliminates the need to remember using
ctx.resume()andctx.yield(), at the cost of being less flexible.