Wait with a non-number or non-tonumber-able value is the same as wait()

When wait is called with a non-number and non-tonumber-able value (such as wait({}) or wait(workspace)), it produces the same behavior as wait(). I would expect such values to cause an error to help me debug my code.

This bug happens on www/production.

For reference, wait(5), wait(), and wait(nil) are what I personally expect to work. I would expect wait("5") has to be kept for backwards-compatibility reasons.

Edit: This also applies to many old APIs, such as constructors for old types.


I can see an argument that this may break existing scripts. It’s unlikely that wait is often used with such invalid parameters, and Roblox does have the ability to track such usage in online games. It would be great if we could narrow down the allowed types to only the ones people use.


To gauge developer expectations and – mostly – to satisfy my curiosity, I’ve built a poll:

  • I expect wait(5) to work
  • I expect wait("5") to work
  • I expect wait() to work
  • I expect wait(nil) to work
  • I expect wait(false) to work
  • I expect wait(true) to work
  • I expect wait("hello") to work
  • I expect some other types to work
  • I expect all other types to work

0 voters

I actually think this is not part of the wait method but more so Lua doing automatic conversion from String to Float as wait takes a numeric value. Same as doing print(5 + "5")

> print(tick()) wait("5") print(tick())
1518282732.9212
1518282737.9342
> print(tick()) wait("hello") print(tick())
1518282766.4087
1518282766.4516

After all ā€˜hello’ is interpreted as nil as you state, but ā€˜5’ is taken as the float 5.0 as there is a 5 second wait.

I’m thinking similarly, but that this is something Roblox did intentionally years ago, not a side-effect of using Lua. The stance on string to number conversions has since changed since 2006 or so.


For example, TweenInfo.new("7") spews out TweenInfo.new first argument expects a number for time.

On the other hand, one of the original Roblox types, Vector3, allows any argument type:
Vector3.new({}, 50, workspace) works, just like wait({}) or wait(workspace).


Clearly, Roblox can optionally convert to a number. I’m thinking Roblox uses Lua’s tonumber internally, then if the argument is nil afterwards, it sets it to 1/30.

If this is addressed, Roblox will…

  • Check the type
    • If it’s a number, use it
    • If it’s nil or empty, set it to 1/30
    • If it’s any other allowed type, set it to 1/30
    • If it’s a string, tonumber it
      • If the result is nil, error.
    • If it’s any other type, error.
1 Like

I think the confusion comes from Lua by default trying to fit the parameters in the function definition; if a definition does include a string as possible parameter Lua will not convert it. If that definition then checks whether the given argument is a string and spews an error message if it does, that is on Roblox’ side.

function wait(a)
-- some operation that only works on floats
end

as opposed to

function vector3.new(a,b,c)
-- code that handles a,b and c as any type
end

The result would be any function that can accept a float will have its arguments converted to a float by Lua, where any function that accepts strings gets the raw argument input if it is a string. Whether Roblox decides to make it error after checking it indeed is a String should be irrelevant.

Lua does no type checking when switching boundaries.
Usually interop happens between functions in a manner that you get this sort of styled code:
https://hastebin.com/uxariganof.c

The type checking is completely up to the Roblox developers.

1 Like

This is feels really unfriendly for debugging code and should have never worked in the first place. Honestly, if possible, I would want wait(nil) to error too in the off chance you have a variable that’s supposed to be a number, but is somehow nil from a bug in your code. Though this seems like the most controversial one of all.

I’ve never ran into an issue like this but I can imagine how annoying it would have been to debug if I had. This should only work with a number or nothing.

Oh, I was under the impression it does match definitions with given arguments dynamically. That would mean @Corecii is right, my bad. That does leave me a bit uncertain how and more specifically where Lua does the string to number conversion, unless it is part of every standard library method/operator.

Lua 5.1 does have an entire file dedicated to interop with C to Lua and Lua to C, if you’re in for a good read.

1 Like

Keep in mind that wait and Vector3 aren’t written in Lua, they’re written in C or C++. Some information about this can be found here.

Based on this page, it looks like Roblox has to make a clear, explicit, conscious decision to either convert to a number or not. it doesn’t look like Lua automatically converts to a number based on declared argument types.

This is why TweenInfo has different behavior from wait and Vector3 – Roblox chose to check the type for TweenInfo, and convert the type for wait and Vector3.


With this information…

Only if lua_tonumber is used, which is not automatic.

It’s useful to have error messages that tell you when you provided the wrong type. If I’m providing a table to wait, which of the following is more likely?:

  1. My code has a bug that caused it to provide a table rather than, say, a number in the table
  2. I wanted the table to be converted to a number

If we’re on the same page, then we both agree that #1 is more likely. Because of this, we can error when a non-number is provided, which provides developers a useful debugging tool.

This is the appeal of strongly-typed and static-typed languages: you can’t provide a wrong type. If you do, you have a bug, and it’s detected before your code even runs. We sadly can’t have these compile-time checks in Lua, but we can do them at run-time.

Roblox has chosen to do these checks at run-time due to its value as a debugging tool. For example, these new APIs error for non-numbers:

  • TweenInfo.new("hello")
    • TweenInfo.new first argument expects a number for time.
  • NumberSequence.new("hello")
    • NumberSequence.new(): table of NumberSequenceKeypoints expected. (where NumberSequence.new(1) works)
  • NumberSequenceKeypoint.new(1, "hello")
    • bad argument #2 to 'new' (number expected, got string)
  • Random.new():NextInteger(1, "hello")
    • bad argument #2 to 'NextInteger' (number expected, got string)
  • Even one of the new APIs for CFrame, one of the oldest types, does not accept arbitrary inputs: CFrame.fromAxisAngle(Vector3.new(), "hello")
    • bad argument #2 to 'fromAxisAngle' (number expected, got string)

Worth noting that all of these APIs except TweenInfo accepted ā€œ5ā€

Correct, what had me confused is the idea that before the arguments are passed to the corresponding C-call the arguments could be dynamically altered to match, given Lua is not strongly typed. This in itself is a bit silly given it should be impossible to actually know what the function is expecting, all that is given is a stack if I am not mistaken.

As for whether it has its use to provide error messages I definitely do agree, I just wondered whether it really was up to Roblox, which it appears to be.

1 Like

I don’t believe this should be in bug reports, as it’s not really a bug, but it’s the way Lua functions. Passing a non-number object through wait treats it as passing nil through wait, causing it to return with no arguments, like @TaaRt explained.

lua_tonumber() returns 0.0 if the argument can’t be coerced to a number. Which is why it assumes 0 when used with {}.

1 Like

Ah, alright

lets just pretend I was right lol