Creating Custom Datatypes with ModuleScripts!

Introduction


Hi! Welcome to my first tutorial! We’ll be seeing how to create custom datatypes in Roblox!

What are datatypes?

According to Wikipedia:

In computer science and computer programming, a data type or simply type is an attribute of data which tells the compiler or interpreter how the programmer intends to use the data.

In a Roblox context, datatypes tell Roblox how to run and change the game experience. A full list of Roblox-native datatypes can be found at this handy little page on the Developer Hub:
DataType Index


Custom Datatypes


As of time of writing (2021-07-02T23:07:00Z), there’s no Luau-native way to create custom datatypes. However, using a ModuleScript and some clever dictionary usage, you can emulate custom datatype behavior pretty well. So let’s get started!

Decide on what your datatype will be. I’ve decided on a relatively simple datatype for this tutorial: a “UFrame” (Universal Frame) datatype, essentially a cross between CFrame and Vector3. It will be composed of the components of 2 Vector3s: one for position, and the other for orientation.

Now that you’ve decided on your datatype, create a ModuleScript in ServerStorage. Rename the ModuleScript something descriptive. I’ll rename it “UFrame”. Open the module if it’s not yet, and replace where it says “module” with anything. Again, I’ll replace it with “UFrame”.

local UFrame = {}

return UFrame

To start, create a constructor function. I personally use .new(), as it’s widely used on the Roblox platform.

local UFrame = {}

function UFrame.new()
    
end

return UFrame

Use the components of your datatype as parameters, and create a dictionary for the constructor function to return.

local UFrame = {}

function UFrame.new(pX, pY, pZ, rX, rY, rZ)
    local ReturningData = {}
end

return UFrame

Now populate the dictionary with components. This will be your datatype. You will return this datatype to the calling script.

local UFrame = {}

function UFrame.new(pX, pY, pZ, rX, rY, rZ)
    local ReturningData = {}
    ReturningData.pX = pX
    ReturningData.pY = pY
    ReturningData.pZ = pZ
    ReturningData.rX = rX
    ReturningData.rY = rY
    ReturningData.rZ = rZ
    return ReturningData
end

return UFrame

That is basically your datatype! But at the moment, it’s pretty useless. It’s just a dictionary, and not a very long one at that. It doesn’t even define any languages! But to extend the datatype’s functionality, you can integrate functions! Here’s how.

Where does this happen?

Functions in datatypes occur in Roblox natively. Good examples are the Vector3 functions:

  • Vector3:Lerp()
  • Vector3:Dot()
  • Vector3:Cross()
  • Vector3:FuzzyEq()

These aren’t in the Vector3 library. They can be called like:

Part.Position:Cross(OtherPart.Position)

I want to add a UFrame:Apply(BasePart) function, so I do the following. This is the same format for your own custom datatype functions.

local UFrame = {}

function UFrame.new(pX, pY, pZ, rX, rY, rZ)
    local ReturningData = {}
    ReturningData.pX = pX
    ReturningData.pY = pY
    ReturningData.pZ = pZ
    ReturningData.rX = rX
    ReturningData.rY = rY
    ReturningData.rZ = rZ
    function ReturningData:Apply(BasePart: BasePart)
        BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
        BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
    end
    return ReturningData
end

return UFrame

That is how you add functions to custom datatypes!

Now, I want to add a UFrameLibrary:GetBasePartUFrame(BasePart) function. You likely can deduce how to do that. Add a function to the module. This will be the same framework to use when returning your datatype from a function of your own datatype.

local UFrame = {}

function UFrame.new(pX, pY, pZ, rX, rY, rZ)
    local ReturningData = {}
    ReturningData.pX = pX
    ReturningData.pY = pY
    ReturningData.pZ = pZ
    ReturningData.rX = rX
    ReturningData.rY = rY
    ReturningData.rZ = rZ
    function ReturningData:Apply(BasePart: BasePart)
        BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
        BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
    end
    return ReturningData
end

function UFrame:GetBasePartUFrame(Part: BasePart)
    local pos = Part.Position -- Shortened
    local ori = Part.Orientation
    return UFrame.new(pos.X, pos.Y, pos.Z, ori.X, ori.Y, ori.Z)
end

return UFrame

You can even return data from in-datatype functions!

local UFrame = {}

function UFrame.new(pX, pY, pZ, rX, rY, rZ)
    local ReturningData = {}
    ReturningData.pX = pX
    ReturningData.pY = pY
    ReturningData.pZ = pZ
    ReturningData.rX = rX
    ReturningData.rY = rY
    ReturningData.rZ = rZ
    function ReturningData:Apply(BasePart: BasePart)
        BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
        BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
    end
    
    function ReturningData:Add(otherUF: UFrame)
        local newpX = ReturningData.pX + otherUF.pX
        local newpY = ReturningData.pY + otherUF.pY
        local newpZ = ReturningData.pZ + otherUF.pZ
        local newrX = ReturningData.rX + otherUF.rX
        local newrY = ReturningData.rY + otherUF.rY
        local newrZ = ReturningData.rZ + otherUF.rZ
        return UFrame.new(newpX, newpY, newpZ, newrX, newrY, newrZ)
    end
    
    return ReturningData
end

function UFrame:GetBasePartUFrame(Part: BasePart)
    local pos = Part.Position -- Shortened
    local ori = Part.Orientation
    return UFrame.new(pos.X, pos.Y, pos.Z, ori.X, ori.Y, ori.Z)
end

return UFrame

Now you know how to create custom datatypes! Please give suggestions and thanks for reading!

1 Like

You can apply your functions in a separate outside table if you want to separate object methods from the main class table and make use of metatables. Makes it a whole lot more readable and reusable that way. At least I prefer this way over adding my methods in via the constructor.

local UFrameClass = {}
local UFrame = {}

function UFrame:Apply()
end

function UFrame:Add()
end

function UFrameClass.new(...)
    local uFrameObject = setmetatable({}, UFrame)
    -- Set properties
    return uFrameObject
end

-- If you like keeping in tune with Roblox convention and not using colon
-- when you don't use self, this would be a more accurate constructor.
function UFrameClass.fromBasePart(...)
    return UFrame.new(...)
end

return UFrameClass
10 Likes

So firstly you should look into metatables. You could easily replace your :Add() method with a metamethod. Secondly, Roblox’s Lua-derived scripting language, Luau, supports partial typechecking and with that comes type definitions. So if you had a UFrameClass you could actually define a type for that class as such:

type UFrameType = UFrame.new(...)
local someUFrame: UFrameType = UFrame.new(...) -- Type inference does exist so you don't have to explicitly state the type here

Lua by definition is a weakly typed language. Luau provides these typechecking annotations to ease that problem. The tutorial you provided is not really a datatype for a few reasons. First you’re not strictly enforcing/defining your UFrame type. The operations provided also aren’t safe as expected with datatypes. Also as stated above there’s no reason not to use Luau’s typechecking system for defining your types considering it’s typesafe at compile time rather than run time.

In your :Add() method the argument you take is of type UFrame but that won’t produce what you expect. The UFrame type is actually a table containing the .new() and :GetBasePartUFrame() functions not an actual UFrame value.

5 Likes

sorry if this is reviving a dead thread but you can create custom types using the type keyword but as of yet i currently have 0 idea how to add functions and other actual usefull things to types defined with that keyword
and as a note. this was added in may 2020 so it did exist at the time of writing
Luau Type Checking Release - Updates / Announcements - DevForum | Roblox
Type checking - Luau (luau-lang.org)