Skip to main content

LuaTuple<T>

The Problem

Given the following Luau code, how do we type it?

local function foo()
return "abc", 123
end

Multiple returns are a common occurrence in Luau, but not really a concept in TypeScript. TypeScript does have a feature called "tuples" which allow for fixed length array types with an individual type for each index.

So we might type this as:

declare function foo(): [string, number];

However, this is not correct! TypeScript tuples are arrays, not multiple returns. It would expect the Luau to be return { "abc", 123 }.

The Solution

To solve this problem, roblox-ts introduces a special type called LuaTuple<T>. This is typed as:

type LuaTuple<T extends Array<any>> = T & { readonly LUA_TUPLE: never };

LUA_TUPLE is used to ensure the type name is not lost or cast into an Array<T> accidentally.

When the compiler sees this type as a function return type, it can infer that it's meant to be a multiple return.

In general, this type is used for typing existing Luau modules or the Roblox API.

Examples

If you immediately destructure the result, it will be compiled into a simple variable declaration from a multiple return.

const [actualTimeYielded, totalTime] = wait(1);

If you do not destructure the result, the compiler will wrap the return in { } and turn the result into an array object.

const result = wait(1);
const actualTimeYielded = result[0];
const totalTime = result[1];

You can also index the result immediately after the function call and receive an optimized emit:

import { Players } from "@rbxts/services";

// .Wait() here returns LuaTuple<[character: Model]>,
// so we need to use `[0]` to grab the first (and only) element.
const character = Players.LocalPlayer.Character || Players.LocalPlayer.CharacterAdded.Wait()[0];

Using LuaTuple<T> in Your Own Code

The compiler supports using LuaTuple<T> in outside of type definitions, but it's generally not recommended. However, if you're writing code that is consumed by an existing Luau module this can be useful!

Fortunately, we have a macro called $tuple that covers multiple returns so that you don't need to type assert the function return yourself every time you want it to be a tuple.

function hasMultipleReturns() {
// this will compile into `return "abc", 123`
return $tuple("abc", 123);
}