Documentation on BindableFunctions and Events doesn't mention table-duplication behavior

Whenever a table is passed through a BindableFunction or BindableEvent, it is copied before being sent on to the receiver, rather than being passed by reference. The relevant documentation doesn’t explain that this happens, which bugged me when I tried to use it with object-oriented programming.

More experienced developers can infer that this documentation’s stated restrictions means that JSON might be used, which in turn means tables are copied. Though newer developers who learned object-oriented programming before JSON will have a hard time without knowing these bindables duplicate tables.

Here’s a script that reproduces the table-duplication behavior.

-- Set up the system for testing.
OriginalTable = {Foo = "Hello."}
PassedTable = nil
ReturnedTable = nil
EventTable = nil

PassInTest = Instance.new("BindableFunction")
PassInTest.OnInvoke = function(t)
	PassedTable = t
end
PassOutTest = Instance.new("BindableFunction")
PassOutTest.OnInvoke = function()
	return OriginalTable
end
EventTest = Instance.new("BindableEvent")
EventTest.Event:Connect(function(t)
	EventTable = t
end)

-- Begin testing.
print("--- Passing table to a BindableFunction. ---")
print("Original table -", OriginalTable)
PassInTest:Invoke(OriginalTable)
print("Function sees -", PassedTable)
print("Same? -", OriginalTable == PassedTable)

print("--- Getting table from another BindableFunction. ---")
print("Original table -", OriginalTable)
ReturnedTable = PassOutTest:Invoke()
print("Returned table -", ReturnedTable)
print("Same? -", OriginalTable == ReturnedTable)

print("--- Passing table through BindableEvent. ---")
print("Original table -", OriginalTable)
EventTest:Fire(OriginalTable)
print("Handler sees -", EventTable)
print("Same? -", OriginalTable == EventTable)

print("--- Editing Foo in table seen by BindableEvent. ---")
print("Original -", EventTable.Foo)
EventTable.Foo = "Good bye."
print("New -", EventTable.Foo)
print("Value in original table changed? -", OriginalTable.Foo == "Good bye.")

Output:

— Passing table to a BindableFunction. —
Original table - table: 0x7f472d427fddab75
Function sees - table: 0x88ebc8e8b93a3015
Same? - false
— Getting table from another BindableFunction. —
Original table - table: 0x7f472d427fddab75
Returned table - table: 0x9b0c7b4983c77b65
Same? - false
— Passing table through BindableEvent. —
Original table - table: 0x7f472d427fddab75
Handler sees - table: 0x69d7c09079fafca5
Same? - false
— Editing Foo in table seen by BindableEvent. —
Original - Hello.
New - Good bye.
Value in original table changed? - false

3 Likes

Was about to make a new thread for this, but will bump this instead.

Just ran into this and the documentation still does not mention this behavior.

local tab = {}

print("tab id:", tostring(tab))

local bindable = Instance.new("BindableEvent")

bindable.Event:Connect(function(passedTab)
	print("passed tab id:", tostring(passedTab))
end)

bindable:Fire(tab)

leads to this output, giving two different table ids:

  tab id: table: 0x52c5e57e16394bb3
  passed tab id: table: 0x9be103fb08d66eb3

This would be useful as a warning banner, since this whole behavior can be missed very easily and can lead to potential memory leaks.
In Robloxian High School, we use bindables for our object streaming system; and on object run we pass a Maid/Cleanup through the bindable which can have objects added to be removed with the object being destreamed. These maids were not being cleaned up on object destroy, despite us calling cleanup on them on the system side. Came up as a symptom of this behavior.

This is all that is written about regarding parameters on the BindableEvent page.

3 Likes