Why do Function Parameters have Colons?

I was recently looking at another topic on the Code Review section of the Developer Forum and I noticed something that I’ve seen quite often but never knew the reasoning behind. Why is it instead of using a simple “character” parameter in the anonymous function, they include a colon, with “character: Model”? What is the reason for/purpose of this?

CharacterTrove:Connect(Players.LocalPlayer.CharacterAdded, function(character: Model)
		Character = character
		Humanoid = character.Humanoid :: Humanoid

		connectHumanoid() -- need to run this again because the character is now a new instance
	end)
1 Like

it shows like the autocompleted variables/functions of a parameter

for example

local character: Model;

if u type “character.” (character dot) with this line, it would show everything a instance with class model would have

1 Like

a single colon is a type annotation
a double colon is a type cast/assertion

You should only annotate values that should be a specific type; not values that are already a specific type.

You should only cast a type when a value is guaranteed to be a specific type

looking back at the code snippet

CharacterTrove:Connect(Players.LocalPlayer.CharacterAdded, function(character: Model)
  -- CharacterAdded always returns a character model. CharacterTrove (probably) doesn't know that.

  Character = character -- it is clear that character should be a Model

  Humanoid = character.Humanoid :: Humanoid -- without assertion, character.Humanoid is any
  -- with assertion, it is clear that character.Humanoid is always a Humanoid
  -- (I wouldn't consider this a safe assertion)

  connectHumanoid()
end)

wrote this while gaming. let me know if you need examples
hopefully it makes enough sense

it’s used to define a data type, makes it easier when indexing an object, i personally avoid using them in complex huge, structured data types because it takes so long to set up & i find it very pointless; you can learn more about luau’s data types here @ An introduction to Luau types | Luau

its for giving variables types
I would recommend reading the docs on it

https://create.roblox.com/docs/luau/type-checking

What’s a type assertion versus a type annotation? My understanding so far is that it’s to ensure a variable is of a certain class/datatype, but I’m wondering how I could use it myself in the future fluently.

If character is an object, how would that have anything to do with data types? Is it not just a table?

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

Ok, so key takeaway: it’s only for visuals. Thanks!