Compatblity

One of the big reasons behind the creation of Fuse is safety and ease of development in larger code bases. But another reason is to keep some ideal amount of compatibility with the existing LUA codes other than the ability to load and on this page, we are going to discuss what decisions have been made to achieve this balance between safety and compatibility.

Agenda

Lua language has proven to be one of the most backward-compatible languages that have ever existed and it maintained a surprising amount of backward compatibility when migrating to newer versions which is an astonishing achievement on their side. Unfortunately, it comes at the cost of keeping some of the decisions that made sense in the early days of the language and ever since have lost their usefulness.

Syntax

Fuse syntax tries to be compatible with Lua 5.1 to some extent but we do not want to get 100% compatibility at the cost of inheriting all of the technical debt. Instead, we are aiming for a similar enough language that makes porting current Lua codebases one module at a time possible. To provide such interoperability between Fuse and its target runtimes we have made sure to only support the highest common denominator between all of the different flavors of Lua so we can do things such as writing our code to run on Lua 5.4 in customer’s machine and have it on LuaJIT in production server.

Limited Global Scope

Having global scopes in your files and especially having them as the default behavior of variable definition is reasonable in smaller codebases but since Lua is one of the most loved scripting languages and is so easy to embed in any project we are seeing a multitude of projects that have adopted Lua for their scripting needs as their domain-specific language.

We think that global scopes by default aren’t one of those features that would help us to achieve our goals Since it only makes sense in the context of header files, which may be one of the reasons behind the origin of this decision from Lua team in the birth of the language. It would’ve made sense for the early days of Lua but with the mass adoption of the language, We have seen a lot of abuse via bad use of global scope (or at times naive use).

Other than that it makes it hard to reason about dependencies and the origin of global variables in any given project.

But we cannot entirely remove global variables even tho it makes the inner workings of a reliable module system much easier and straightforward. There are already a lot of libraries written for Lua that are using the global scope and by isolating Fuse from accessing to global scope we are definitely going to break some legacy codes which is something we do not desire.

So what we propose is keeping the global scope but it is no longer the default scope of our variables, For example instead of doing the following in the Lua:

global_variable = "I'm a global variable"
local local_variable = "I'm a local variable"

We write this:

global_variable = "It will raise an error in compilation!"
global global_variable = "I'm a global variable"
const local_constant = "I'm a local constant"
-- or
let local_variable = "I'm a local variable"

As you can see in Fuse we have to explicitly define a variable as global with the keyword otherwise we get an error since it is the same as trying to write to an undefined variable.

We also don’t allow access to global variables via the use of their name alone, Since global variables are side effects in nature and are unpredictable in the code what variable and with which type exists at this point in the program. These variables are really hard to keep track of since they can be defined inside of functions, files, chunks, or many other possible places. To accommodate this issue we only allow users to access global variables using the global keyword or _G table.

As a result instead of writing this:

my_global = "HI!"

print(my_global)

We do this:

global my_global = "HI!"

print(_G.my_global)
-- or
print(global.my_global)

global when used as a table acts as an alias to _G for readability reasons.

Keep in mind that the _G table always has any type and it is literally impossible to type the global scope at compile time but it is possible to either cast the variables into a known type or create a type definition for global scope and so the compiler can infer the types from that but it won’t guarantee the existence of the given variable and it just provides auto-complete and type checking.

Features beyond Lua 5.1

We have chosen the Lua 5.1 as our base syntax to build upon. This version of Lua is the most available set of features in the Lua ecosystem since anything that works in 5.1 can also work in 5.2, 5.3, 5.4 and Luau but it doesn’t mean that we don’t backport any features from newer versions of Lua or ideas from Luau interpreter that can work as a syntax sugar with our Lua 5.1 limitation that we applied to ourselves. There are a few notable changes. Mainly we don’t have pack and unpack functions anymore, instead, we are using table.pack and table.unpack to make it consistent between different target versions. Other than that there are also some new features backported from newer releases of Lua and some completely new features that are missing from every version of Lua, Things like type support, string interpolation, traits, pattern matching, array deconstruction, += -= *= /= operators and better error handling(see Error Handling section) among many others.

Differences with Lua 5.2

Feature Status Notes
yieldable pcall/xpcall Yes part of the @fuse libraries
yieldable metamethods Partial part of the @fuse libraries
ephemeron tables No limitation of LuaJIT and Lua 5.1, but @fuse:collections comes with a handy WeakMap data type to use
bitwise operators Yes use of bit32 in Lua 5.2+ and Luau, @fuse:bit32 for Lua 5.1 and LuaJIT
light C functions Partial depends on the runtime, if your target supports it Fuse also supports it
emergency garbage collector Future it is a runtime-specific problem, we will support it for runtimes that have it(even Lua5.1 can be patched for it)
goto No it complicates the control flow of the program and isn’t even supported universally across the Lua ecosystem
table finalizers No we do not support this since many of our target runtimes don’t have it(Lua 5.1, LuaJIT, Luau)
fenv Partial can only be used when targeting Lua 5.1, LuaJIT, and Luau(see preprocessor #if section)
tables honor the __len metamethod Yes* Fuse code will respect it no matter what but when using compiled Fuse code from Lua 5.1 it won’t get respected(use t:len() method or lenOf t function defined in the fuse-runtime library)
hex and \z escapes in string Yes  
support for hexadecimal floats Yes  
order metamethods are called for unrelated metatables Yes* Fuse code will respect it but it won’t work when used in the Lua 5.1 runtime
empty statement Yes  
break statement may appear in the middle of a block Yes  
no more verification of opcode consistency Partial it depends on the runtime and Fuse doesn’t neither improve nor prevent it
hook event “tail return” replaced by “tail call” Unknown right now debug is dependent on the runtime but with the introduction of @fuse:debug it will get unified across all runtimes
arguments for function called through xpcall Yes  
optional base in math.log Yes Fuse uses it’s own math module @fuse:math
optional separator in string.rep Yes Fuse uses its own string module @fuse:string
file:write returns file No as it is right now we do not support it, this is because of limitations in some of our target runtimes
closing a pipe returns exit status Partial depends on the runtime
os.exit may close state Partial depends on the runtime
new metamethods __pairs and __ipairs Future* in future releases of Fuse it will be supported inside the Fuse code itself, but won’t work when iterated from Lua 5.1, LuaJIT and Luau
frontier patterns Future it will be supported in future releases of Fuse, TBD
%g in patterns Future it will be supported in future releases of Fuse, TBD
\0 in patterns Future it will be supported in future releases of Fuse, TBD
debug.getlocal can access function varargs Unknown right now debug is dependent on the runtime but with the introduction of @fuse:debug it will get unified across all runtimes
string.gsub is stricter about using % on special characters only Future it will be supported in future releases of Fuse, TBD
NaN keys are supported for tables with __newindex Future it will be supported in future releases of Fuse, TBD

* items whose status is marked with an asterisk are supported in Fuse but don’t have universal support when used from some versions of vanilla Lua

Differences with Lua 5.3

Feature Status Notes
\u escapes in strings Yes  
integers (64-bit by default) No it will break our compatibility with most of our target runtimes
bitwise operators Yes without 64-bit integers, it is the same as operators in Lua 5.2 which is supported in Fuse
basic utf-8 support Yes we have full support for utf-8 strings(see ustring type)
functions for packing and unpacking values (string.pack/unpack/packsize) Yes support via @fuse:string library
floor division Yes  
ipairs and the table library respect metamethods Yes* Fuse code will respect them but it won’t work when used from Lua runtimes before 5.3
new function table.move Yes  
collectgarbage("count") now returns only one result Yes  
coroutine.isyieldable Unknown it will be determined in the future, right now we do not support it but we are working towards a universal implementation
stricter error checking for table.insert/table.remove Partial we have them depending on the target runtime but we may add it into the Fuse language if we improve the performance cost
__eq metamethod is called for unrelated metatables Yes* Fuse code will respect it but it won’t work when used from Lua runtimes before 5.3

* items whose status is marked with an asterisk are supported in Fuse but don’t have universal support when used from some versions of vanilla Lua

Differences with Lua 5.4

Feature Status Notes
new generational mode for garbage collection Partial it depends on the target runtime, and will only work in Lua 5.4
to-be-closed variables Unknown we are working on a solution to support it without depending on the language features, but even if pull it off the syntax is going to be different(since we already use macros as attributes like Rust)
const variables Yes we offer both const and let keywords as opposed to using attributes with local keyword
new implementation for math.random Partial right now it depends on the runtime but to make randoms more consistent across runtimes we are going to replace it in the future with our own @fuse:math library
optional init argument to stirng.gmatch Future it doesn’t exists on the current version but we are working on it
new functions lua_resetthread and coroutine.close Unknown we are not sure whether it is possible or not
string-to-number coercions moved to the string library Unknown it may break compatibility with older versions and needs more time in the oven before we can make the final verdict
new format %p in string.format No no language support before 5.4
utf8 library accepts codepoints up to 2^31 No if we find enough use cases we will start working on it
function print calls __tostring instead of tostring to format its arguments Yes  
decoding functions in the utf8 library do not accept surrogates Yes  

Differences with Luau

Feature Fuse Support Luau Support Notes
io, os, package, and debug library Full Partial Luau lacks this because of sandboxing; when Fuse targets Luau runtime it is confined to the environment of Luau
loadfile, dofile Full Partial Luau lacks this because of sandboxing; when Fuse targets Luau runtime it is confined to the environment of Luau
loadstring, string.dump Full Partial Luau lacks this because of sandboxing; when Fuse targets Luau runtime it is confined to the environment of Luau
newproxy Full Partial Luau lacks this because of sandboxing; when Fuse targets Luau runtime it is confined to the environment of Luau
yieldable pcall/xpcall Yes Yes  
yieldable metamethods Partial No  
ephemeron tables No No Fuse comes with a WeakMap data structure in its standard library which can mimic this to some extent
emergency garbage collector No No both Luau and Fuse are lacking this feature, we may add support for it down the road when targeting Lua 5.2+
goto statement No No the few use cases for goto dosn’t worth all of the bad uses of it that may happen!
finalizers for tables No No  
no more fenv for threads or functions Partial No fenv still exists when targeting Lua 5.1, LuaJIT, and Luau but can only be used in preprocessor #if for one of these runtimes
tables honor the __len metamethod Yes Yes Fuse respects this metamethod but if the table gets used from Lua 5.1 or LuaJIT it will be ignored. In those cases users should use t:len() or lenOf t
hex and \z escapes in strings Yes Yes  
support for hexadecimal floats Yes No  
order metamethods are called for unrelated metatables Yes No Fuse code will respect it but it won’t work when used from the Lua 5.1 code
empty statement Yes No  
break statement may appear in the middle of a block Yes No Luau have encountered dragons on this path, Fingers crossed we hope to conqueror it
arguments for function called through xpcall Yes Yes  
optional base in math.log Yes Yes  
optional separator in string.rep Yes No  
new metamethods __pairs and __ipairs Future No in future releases of Fuse it is going to be possible to define these metamethods and Fuse would respect them but it won’t work when used from Lua 5.1 or Luau code
frontier patterns Future Yes  
%g in patterns Future Yes  
\0 in patterns Future Yes  
bit32 library Yes Yes  
string.gsub is stricter about using % on special characters only Future Yes  
light C functions Partial No Fuse depends on the runtime to provide light C functions
NaN keys are supported for tables with __newindex Future Yes  
\u escapes in strings Yes Yes  
integers (64-bit by default) No No  
bit64 library No No no need since neither of these 2 languages support 64-bit integers
basic utf-8 support Yes Yes  
functions for packing and unpacking values (string.pack/unpack/packsize) Yes Yes  
floor division Yes Yes  
floor division Yes Yes  
ipairs and the table library respect metamethods Yes No Fuse code will respect them but it won’t work when used from Lua runtimes before 5.3
new function table.move Yes Yes  
collectgarbage("count") now returns only one result Yes Yes  
coroutine.isyieldable Unknown Yes it will be determined in the future, right now Fuse does not support it but we are working towards a universal implementation
stricter error checking for table.insert/table.remove Partial No Fuse have them depending on the target runtime but we may add it into the Fuse language if we manage to improve the performance cost
__eq metamethod is called for unrelated metatables Yes No Fuse code will respect it but it won’t work when used from Lua runtimes before 5.3
new generational mode for garbage collection Partial In the Work for Fuse it depends on the target runtime, and will only work when targeting Lua 5.4, Luau is also working on implementing a better GC
to-be-closed variables Unknown No we are working on a solution to support it without depending on the language features, but even if pull it off the syntax is going to be different(since we already use macros as attributes like Rust)
const variables Yes No Fuse offers both const and let keywords as opposed to using attributes with local keyword
new implementation for math.random Partial Yes right now Fuse depends on the runtime but to make randoms more consistent across runtimes we are going to replace it in the future with our own @fuse:math library
optional init argument to stirng.gmatch Future No  
new functions lua_resetthread and coroutine.close Unknown Yes  
string-to-number coercions moved to the string library Maybe No  
new format %p in string.format No No  
utf8 library accepts codepoints up to 2^31 No No if we find enough use cases we will start working on it
The use of the __lt metamethod to emulate __le has been removed Partial No when targeting the Lua 5.4 it is removed otherwise it is still there for compatibility reasons
When finalizing objects, Lua will call __gc metamethods that are not functions No No  
function print calls __tostring instead of tostring to format its arguments Yes Yes  
decoding functions in the utf8 library do not accept surrogates Yes Yes