ROBLOX provides many value types other than the Lua primitives, so it should
be possible to distinguish these types beyond Lua userdata. Like the type
function, a similar function which distinguishes these types should be
implemented. Such a function could be used to enforce stricter typing of
values, improving error detection and logging.
The name of the function could be some variation of "roblox" and "type". These should be enough to convey the purpose of the function. A short name is preferred, since the usage of the function is meant to be primitive. This proposal will use "rbxtype" to refer to the function, and for examples.
rbxtype(value) string
Receives any value, and returns the value's internal ROBLOX type as a string.
rbxtype is meant to be used in the context of ROBLOX types. A value may have
more type-like information related to it, but that does not exist as far as
rbxtype is concerned. Once a basic type is determined by rbxtype, further
information can be determined using whatever APIs are available.
The following subsections describe how certain types should be handled.
Passing a Lua type (e.g. numbers, strings, tables) should return a single
distinct type (e.g. rbxtype(12) == 'LuaValue'). Once the value has been
determined as a Lua type, more information can be determined with the type
function.
This is mostly for the sake of consistency. In the context of Lua scripts, Lua
types are more primitive, so it would generally not be necessary to use
rbxtype to verify a Lua type.
Any ROBLOX instance (e.g. BasePart, Script, Workspace) should return a single
type (e.g. rbxtype(Instance.new('Part')) == 'Instance'). Once a type has
been determined as an Instance, more information can be determined with the
ClassName property or the IsA function. Example:
function VerifyClass(value, class)
assert(type(class) == 'string')
if rbxtype(value) ~= 'Instance' then
error("argument must be an Instance", 2)
end
-- Value is an Instance; IsA can be used successfully.
if not value:IsA(class) then
error("argument must be a " .. class, 2)
end
end
function ManipulatePart(part)
VerifyClass(part, 'BasePart')
-- Modify property inherited by BasePart class.
-- The use of VerifyClass guarantees that the
-- property will be indexed successfully.
part.Position = Vector3.new(0,0,0)
endTo continue with the pattern, enums and enum items should each return a single value. For example:
rbxtype(Enum.NormalId) == 'Enum'rbxtype(Enum.NormalId.Front) == 'EnumItem'rbxtype(Enum) == 'EnumContainer'
Unfortunately, there isn't a complete API that can be used to get further
information about them. EnumItems have a Name and Value property, but that's
about it. Currently, more info can be gathered by parsing the results of a
call to tostring. However, that's more of a hack than a real API. Here's a
sub-proposal:
- Add a function to enums that simply returns the name of the enum (e.g.
Enum.NormalId:GetName() == 'NormalId'). It should be a function, opposed to a property, so that it does not conflict with the indexing of EnumItems (e.g.Enum.NormalId.Front). - Add a property to enum items that returns the associated enum (e.g.
Enum.NormalId.Front.Enum == Enum.NormalId). Returning the direct value is preferred, rather than just the name, so that it is not necessary to get the actual enum by indexing theEnumcontainer.
Example:
function VerifyEnum(value, enum)
assert(rbxtype(enum) == 'Enum')
if rbxtype(value) ~= 'EnumItem' then
error("argument must be an EnumItem", 2)
end
if value.Enum ~= enum then
error("argument must be a " .. enum:GetName(), 2)
end
end
function SetFace(surfaceGui, face)
VerifyClass(surfaceGui, 'SurfaceGui')
VerifyEnum(face, Enum.NormalId)
surfaceGui.Face = face
endFor completeness, the Enum container should also return some kind of type.
Unlike the type function, which throws an error when no value is passed, it
might be convenient to return "void" when no value is passed to rbxtype.
However, it would be less consistent with type. The choice would probably
depend on how no-value is handled internally.
If the type of a value cannot be determined, it should return a distinct value indicating such (e.g. "UnknownType").
rbxtype() == "void"
rbxtype(nil) == "LuaType"
rbxtype(false) == "LuaType"
rbxtype(12) == "LuaType"
rbxtype("hello") == "LuaType"
rbxtype({1,2,3}) == "LuaType"
rbxtype(function()end) == "LuaType"
rbxtype(coroutine.create(function()end)) == "LuaType"
rbxtype(newproxy()) == "LuaType"
rbxtype(Axes.new()) == "Axes"
rbxtype(BrickColor.new()) == "BrickColor"
rbxtype(CFrame.new()) == "CFrame"
rbxtype(Color3.new()) == "Color3"
rbxtype(Faces.new()) == "Faces"
rbxtype(Ray.new()) == "Ray"
rbxtype(Region3.new()) == "Region3"
rbxtype(Region3int16.new()) == "Region3int16"
rbxtype(Vector2.new()) == "Vector2"
rbxtype(Vector2int16.new()) == "Vector2int16"
rbxtype(Vector3.new()) == "Vector3"
rbxtype(Vector3int16.new()) == "Vector3int16"
rbxtype(UDim.new()) == "UDim"
rbxtype(UDim2.new()) == "UDim2"
rbxtype(Game) == "Instance"
rbxtype(Workspace) == "Instance"
rbxtype(Instance.new('Part')) == "Instance"
rbxtype(Instance.new('Script')) == "Instance"
rbxtype(Enum.NormalId) == "Enum"
rbxtype(Enum.NormalId.Front) == "EnumItem"
rbxtype(Enum) == "EnumContainer"
rbxtype(Game.Changed) == "RBXScriptSignal"
rbxtype(Game.Changed:connect()) == "RBXScriptConnection"
rbxtype(UnknownValueType) == "UnknownType"