Why do Function Parameters have Colons?

annotation

  • is for expecting a specific type
  • defines what a variable is allowed to be
  • helps you find bugs

assertion

  • is for overriding the linter (tells the script what a value definitely is)
  • it helps you hide warnings

neither of them can ensure/change a type. they are purely visual in luau


just to be clear, you don’t need to strictly write strict code unless you want sexy code (normal code can be too).
strict code = more explict code. the benefit is predictability.

you should check the docs type checking @TaxFraudBruh already suggested this.

start with annotating your function parameters. it’s great for ModuleScripts


yes everything is a table but classes are treated as data types


i still want to emphasize that you should not cast types that aren’t guaranteed.

i’ll try to make some simple examples

unsafe casting/assertion:
saying Tool is always a Tool may give you an unexpected error during a play test. there won’t be a warning

--!strict
local Player = game.Players.LocalPlayer :: Player -- This is fine. LocalPlayer exists in LocalScripts

local Username = "BonesIsUseless"
local Money = 0
local MaxHealth = 20

local function FindTool(Username: string, ToolName: string): Tool?
  local Backpack = Player:FindFirstChild("Backpack") :: Instance -- Backpack exists
  
  -- NOT SAFE: What if "God Sword" doesn't exist? 
  -- This forces 'Tool' to be a Tool even if it is nil.
  local Tool = Backpack:FindFirstChild(ToolName) :: Tool 
  print(Tool.Name)
  
  return Tool
end

-- If i have no "God Sword", this will error when printing (nil).Name
local FavoriteWeapon = FindTool(Username, "God Sword")

safe casting:
here, only Player and Backpack gets asserted types
Tool’s class gets checked with a guard statement

--!strict
local Player = game.Players.LocalPlayer :: Player -- LocalPlayer exists in LocalScripts

local Username = "BonesIsUseless"
local Money = 0
local MaxHealth = 20

local function FindTool(Username: string, ToolName: string): Tool?
  local Backpack = Player:FindFirstChild("Backpack") :: Instance-- Backpack exists

  -- It would be better to check if the item exists, and if it's a tool
  local Tool = Backpack:FindFirstChild(ToolName)

  if not Tool or not Tool:IsA("Tool") then 
    warn(Username, "has no", ToolName, "tool") 
    return nil
  end

  print(Tool.Name)

  return Tool
end

-- FavoriteWeapon's type should now be Tool?
local FavoriteWeapon = FindTool(Username, "God Sword")

redundant annotations:

--!strict
local Username: string = "BonesIsUseless"
local Money: number = 99999999999999
local MaxHealth: number = 20

helpful annotations:
annotating ambiguous Parameters/Variables

--!strict
local Player = game.Players.LocalPlayer :: Player -- LocalPlayer exists in LocalScripts
local Character = Player.Character or Player.CharacterAdded:Wait()

local StarterWeapons: {string} = {"God Sword"}

-- Make it clear that DealDamage requires a Model and Damage (number) value
local function DealDamage(Character: Model, Damage: number)
  local Humanoid = Character:FindFirstChildOfClass("Humanoid")
  if not Humanoid then return end
  
  Humanoid.Health -= Damage
end

-- When using the 'DealDamage' function,
-- you can see that 'Character' should be a Model, and 'Damage' should be a number
DealDamage(Character, 100)

-- This would warn you that it expects a number, not a string
DealDamage(Character, "50") -- This wouldn't error

you do not need to read all of this. handling types is pretty straightforward

2 Likes