Instance:GetAttribute returns void instead of nil when the attribute is not defined

Issue Type: Other
Impact: Moderate
Frequency: Constantly
Date First Experienced: 2021-03-06 00:03:00 (-05:00)
Date Last Experienced:

Reproduction Steps:
The Instance:GetAttribute(attr) function is defined in documentation to return nil if an attribute with the given name is not set on the object. It doesn’t actually do this - the function returns nothing (void), which is inherently different. This can cause unexpected problems if you, for example, use the result of GetAttribute in calling a function which expects a value.

To demonstrate the issue, see the following expression which can be evaluated in the command bar (either by using print or prepending with an equals sign):

type(game:GetAttribute("ThisDoesNotExist"))

The type function expects exactly one value. The type function raises an error: “missing argument #1” because GetAttribute did not return nil. It’s the same thing as just calling type(), which doesn’t make any sense. If GetAttribute properly returned nil, we would’ve seen the string “nil” as a result of the type function, just as if we used type(nil). This is inconsistent with other functions which return nil properly, such as FindFirstChild:

type(game:FindFirstChild("ThisDoesNotExist"))

This expression returns the string “nil” as expected because no such object exists with this name, and FindFirstChild is defined to return nil under these conditions. Unfortunately, GetAttribute does not follow this paradigm.

Consider also the following Lua functions:

function noOp() end
function noOpNil() return nil end

The bug is that GetAttribute behaves like noOp instead of noOpNil when the attribute is not assigned.

It should be noted that this issue is not observable if you were to assign the result to a variable:

local v = game:GetAttribute("ThisDoesNotExist")
print(type(v)) --> nil

Furthermore, if used with the == comparison operator, this issue is also not observable.

game:GetAttribute("ThisDoesNotExist") == nil

Expected Behavior:
Continuing from the example in the repro steps, I expect the actual nil value. Instead, I get what I’m assuming is C++ void instead of Lua nil. It should behave like this function does:

function noOpNil() return nil end

Actual Behavior:
Continuing from the example in the repro steps, I actually get no value at all, causing the described issue. Again, I’m assuming this has to do with C++ void instead of Lua’s nil. It actually behave as if you called it this:

function noOp() end

Workaround:
You can use “or nil” after the function call to achieve the desired effect:

game:GetAttribute("ThisDoesNotExist") or nil
print(type(game:GetAttribute("ThisDoesNotExist") or nil)) --> nil

This doesn’t break under either the current or intended behavior, as non-nil values won’t get replaced by the nil, except void (which subsequently is interpret as nil, and “nil or nil” is just, well, nil).

30 Likes

You can also wrap the result in parentheses.

(game:GetAttribute("ThisDoesNotExist"))
print(type((game:GetAttribute("ThisDoesNotExist")))) --> nil
3 Likes

There are some other APIs that suffer from this issue and are worth including in this report.

  • Plugin:GetSetting("ThisDoesNotExist")
  • DataStore:GetAsync("ThisDoesNotExist")

That said, it seems kind of minor since you’re typically doing something with the value instead of using it directly in another method.

8 Likes

Thanks for the report! We’ve filed a ticket to our internal database and we’ll follow up when we have an update for you.

5 Likes

Has there been any progress on this issue?

no there hasn’t the issue is still there

Just ran into this behavior and ended up really confused what was causing my issue. It’d be really nice if this was fixed.

1 Like

Can confirm that even in early 2023, this issue is still existing. The workaround thankfully still works, however.

Any day now, Roblox.

Still waiting for this to be fixed. It makes even less sense when after you set the attribute to nil, getting the attribute still returns void.

I’m glad I found this bug report, I’ve run into this many times and didn’t know why I was getting odd behavior. As was noted, my workaround was to always assign it to a variable first for proper comparison later on in the code. It would be nice to take out those extra steps. :grin:

1 Like

Unfortunately resolving this is non-trivial as the issue runs much deeper than just GetAttribute. It requires refactoring a portion of our reflection system to treat void differently to null.

It would be great to get an understanding of how impactful this issue is so we can prioritize it accordingly. Is the workaround sufficient? If not, it would be great to get some more detail and examples of exactly what you’re struggling with.

We will fix this eventually but there is no ETA at the moment.

1 Like

I imagine somebody could have a small bug which turns into a huge issue, because they would have never guessed the issue was in GetAttribute. Here’s an example script:

-- in RenderStepped:
local Label = ui.TimeLeft.Title
Label.Text = tostring(workspace:GetAttribute("TimeLeft")) -- error: argument #1 missing
if Label.Text == "nil" then -- Never runs
    Label.Text = "Times up!"
end

The workarounds are very easy to implement, even just making a function to return the attribute should fix the issue. However, a new scripter might not be comfortable with functions and the other workarounds.

Following up here as we believe this has been resolved. Please reach out to me if you run into this issue again though.