Hello developers,
This is an announcement for an upcoming change to script semantics around function object identity. We expect that it generally doesn’t really affect experiences, but there are some odd corner cases in existing experiences that may make them incompatible with this.
In short: If you rely on function object identity and haven’t tested your project in Studio in the last month, you should make sure your experience works correctly in Studio to prepare for this change.
What is the change?
In Luau before this change, and in Lua 5.1, all function()
expressions return a unique, newly created, object. For example, calling foo
multiple times returns multiple different objects:
function foo()
return function() end
end
In Lua 5.2 and 5.3, this behavior changes so that function objects are sometimes shared. Luau is getting a similar change, so that calling foo()
multiple times will return the same function object here.
While the specific semantics are subject to change, an easy way to think about it is that the behavior of calling the function is always going to stay the same. For example, here foo
will never return the same object for different values of arg
, because doing so will break code that executes it:
function foo(arg)
return function() print(arg) end
end
Currently Luau compiler makes a decision to share the functions when the functions don’t have upvalues (that is, don’t refer to locals outside of their scope), or all upvalues are declared at the script level (aka top-level) and aren’t modified after initialization.
This is a very important optimization:
It can often elide function allocations in calls to pcall
, table.sort
and other functional code, as well as object constructors for some OOP code, making code that relies on creating function objects faster to execute.
When will the change go live?
The change has been live in Studio for over a month now. We plan to enable this change for all live experiences on November 1st - since we’ve had this change live in Studio for many weeks and this was mentioned in release notes multiple times, we hope existing experiences already work well with it.
We briefly enabled this change last week, but it caused a subtle bug, so we decided to disable it, post this notice that the change is coming, and enable later.
Please let us know if you have concerns with this change. We understand that this change isn’t completely backwards compatible, but based on our testing almost all the code and almost all the experiences should work fine with this change enabled. We’d like to enable this across the platform to make sure we can realize the performance gains.
What does this change affect?
We know of two cases where the change affects behavior of existing code.
One comes with setfenv
use:
setfenv
can change the environment of an individual function, and if the function is the same, calling setfenv
will modify the same function object. We automatically disable this optimization when the function that creates new functions has a custom environment, so code that uses sandboxing techniques should typically not be affected because it already calls setfenv
before creating new functions.
Another one comes with using functions as table keys:
For example, if you used dummy function objects (function() end
) to act as unique tokens, this code may break as you’ll see the same key being used. We suggest that the code shouldn’t rely on function identity in general, and in this specific case migrates to newproxy()
as a way to generate unique tokens.
Again, please check your projects in Studio and let us know if you have any concerns with this change. We plan to enable it for all experiences on November 1st.