Doing some more thinking about this, I feel like there’s two parts to Instance lifetime clean-up:
- The Roblox-side. This happens at garbage collection.
- The Developer-side. This happens when you remove all Lua data related to an Instance.
Developer-side clean-up always has to happen first.
If you have any Developer-side clean-up, you have to claim control over the Instance’s lifetime. This means that you are claiming to clean up your Developer-side data before expecting the Roblox-side clean-up to happen. If you don’t claim control over the Instance’s lifetime, you will cause memory leaks!
In order to claim control over an Instance’s lifetime, you need an authority on the lifetime. This lifetime authority lets other parts of the codebase detect when an Instance’s lifetime is over and clean-up where appropriate.
The simplest lifetime authority is the Instance’s ancestry. This acts as a built-in, framework-agnostic authority. This is why I suggest using it. This is more-or-less a Roblox-controlled authority that is developer-triggered.
An alternative right now is to have a module acting as the lifetime authority that can trigger clean-up code.
A future alternative will be a semi-standard “LifetimeOver” attribute to act as the Instance’s lifetime authority. To clean-up the Instance, you change the attribute first to trigger Developer-side clean-up, then you Destroy the Instance to trigger Roblox-side clean-up.
What this feature request is suggesting is that the Instance’s Destroy state become a new framework-agnostic lifetime authority. This is not wrong, but it is fundamentally misleading and can lead to hard-to-diagnose memory leaks.
What is misleading here is that using the Destroy state as the lifetime authority implies that Roblox has control over the Instance’s lifetime. It implies that when Roblox-side clean-up happens, Developer-side clean-up will happen too, which implies that Developer-side clean-up will happen even when :Destroy()
isn’t called.
These implications are entirely false. Developer-side clean-up must happen first, so the developer must claim control over the Instance’s lifetime. If the Destroy state is the lifetime authority, then the developer must call :Destroy()
, but this is implied nowhere and can catch many developers with memory-leak bugs.
Contrast the two approaches a third-party module can take:
- “I will try to clean myself up when Destroy is called on the Instance. This means you must call Destroy on the Instance or a memory leak will happen, but I will not expose this requirement in my API at all!”
- “I will only clean up when you call the clean up function. You need to clean up when you’re done with this Instance, and I am exposing this requirement in my API!”
The second option more clearly demonstrates a requirement for the developer to claim control over the Instance’s lifetime. This plays into ideas about API design and how explicitness creates safe, easy-to-use code.
The only reason that detecting Parent = nil
is particularly safe is because it’s common-sense that an object cannot be cleaned-up while it’s somewhere in the game, and because Parent = nil
is a prerequisite for Roblox-side clean-up. Parent = nil
effectively gives you Roblox-authoritative Instance lifetimes with no risk of memory leaks.