Luau unterstützt ein schrittweises Typsystem durch die Verwendung von Typannotationen und Typinferenz. Diese Typen werden verwendet, um bessere Warnungen, Fehler und Vorschläge im Skript-Editor bereitzustellen.
Definiere einen Typ
Verwende das Schlüsselwort type, um deine eigenen Typen zu definieren:
type Vector2 = {x: number, y: number}
Inferenzmodi
Es gibt drei Luau-Typinferenzmodi, die in der ersten Zeile eines Script festgelegt werden können:
- --!nocheck — Überprüfe keine Typen.
- --!nonstrict — Stellt nur sicher, dass Variablentypen angegeben werden, wenn sie explizit annotiert sind.
- --!strict — Stellt alle Typen basierend auf dem inferierten oder explizit annotierten Typ sicher.
Die Modi --!nonstrict und --!strict kontrollieren, wie streng der Typprüfer bei der Inferenz und Überprüfung von Typen für Variablen und Funktionen ist. Alle Typmissergebnisse in Skripten werden im Skript-Editor hervorgehoben und als Warnungen im Fenster Skriptanalyse angezeigt.
Um einen Standardmodus für alle Skripte festzulegen, den du nach Bedarf überschreiben kannst, siehe Workspace.LuauTypeCheckMode.
Typen
Eine Typannotation kann unter Verwendung des :-Operators nach einer lokalen Variable definiert werden, gefolgt von einer Typdefinition. Standardmäßig wird im nonstrict-Modus allen Variablen der Typ any zugewiesen.
local foo: string = "bar"local x: number = 5
Es gibt vier primitive Typen, die in einer Annotation verwendet werden können:
- nil - kein Wert
- boolean - true oder false
- number - ein numerischer Wert
- string - Text
Innerhalb von Roblox haben alle Klassen, Datentypen und Enums ihre eigenen Typen, gegen die du überprüfen kannst:
local somePart: Part = Instance.new("Part")local brickColor: BrickColor = somePart.BrickColorlocal material: Enum.Material = somePart.Material
Um einen Typ optional zu machen, verwende ein ? am Ende der Annotation:
local foo: string? = nil
Dies erlaubt es der Variablen, entweder den angegebenen Typ (in diesem Fall string) oder nil zu sein.
Literale Typen
Du kannst auch Zeichenfolgen und Booleans in literale Werte umwandeln, anstatt string und boolean zu verwenden:
local alwaysHelloWorld: "Hello world!" = "Hello world!"alwaysHelloWorld = "Just hello!" -- Typfehler: Typ '"Just hello!"' konnte nicht in '"Hello world!"' umgewandelt werdenlocal alwaysTrue: true = false -- Typfehler: Typ 'false' konnte nicht in 'true' umgewandelt werden
Typumwandlungen
Manchmal musst du den Typprüfer unterstützen, indem du einen Wert mit dem ::-Operator explizit in einen anderen Typ umwandelst:
local myNumber = 1local myString: stringmyString = myNumber -- Nicht OK; TypumwandlungsfehlermyString = myNumber :: any -- OK; alle Ausdrücke können in 'any' umgewandelt werdenlocal myFlag = myNumber :: boolean -- Nicht OK; Typen sind nicht verwandt
Funktionstypen
Betrachte die folgende Funktion:
local function add(x, y)
return x + y
end
Diese Funktion addiert x zu y, gibt jedoch einen Fehler zurück, wenn einer oder beide davon eine Zeichenfolge sind. Luau weiß nicht, dass diese Funktion nur Zahlen verwenden kann. Um diese Art von Problem zu verhindern, füge Typen zu den Parametern hinzu:
local function add(x: number, y: number)
return x + y
end
Luau weiß nun, dass die Funktion zwei Zahlen erwartet, und gibt eine Warnung aus, wenn du versuchst, etwas, das keine Zahl ist, in die Funktion zu übergeben:
add(5, 10)add(5, "foo") -- Typfehler: Zeichenfolge konnte nicht in Zahl umgewandelt werden
Um einen Rückgabewert zu definieren, setze einen :-Operator am Ende der Funktionsdefinition:
local function add(x: number, y: number): number
Um mehrere Typen zurückzugeben, setze die Typen in Klammern:
local function FindSource(script: BaseScript, pattern: string): (string, number)
return 42, true -- Typfehler
end
Definiere einen funktionalen Typ
Ein funktionaler Typ kann mit der Syntax (in) -> out definiert werden. Unter Verwendung der Funktionen aus den vorherigen Beispielen lauten die Typen der Funktionen:
type add = (x: number, y: number) -> numbertype FindSource = (script: BaseScript, pattern: string) -> (string, number)
Tabellentypen
Luau hat keinen table-Typ; stattdessen werden Tabellentypen mit der {}-Syntax definiert. Eine Möglichkeit, Tabellen zu definieren, besteht darin, die {type}-Syntax zu verwenden, die einen Listentyp definiert.
local numbers: {number} = {1, 2, 3, 4, 5}local characterParts: {Instance} = LocalPlayer.Character:GetChildren()
Definiere Indextypen mit {[indexType]: valueType}:
local numberList: {[string]: number} = {Foo = 1,Baz = 10}numberList["bar"] = true -- Typfehler: boolean kann nicht in Zahl umgewandelt werden
Tabellen können auch explizite Zeichenfolgenindizes in einem Typ definiert haben.
type Car = {
Speed: number,
Drive: (Car) -> ()
}
local function drive(car)
-- Immer die Geschwindigkeitsgrenze einhalten
end
local taxi: Car = {Speed = 30, Drive = drive}
Variadische Argumente
Hier ist eine Funktion, die die Summe einer beliebigen Anzahl von Zahlen berechnet:
local function addLotsOfNumbers(...)
local sum = 0
for _, v in {...} do
sum += v
end
return sum
end
Wie erwartet kann diese Funktion jeden Wert annehmen, und der Typprüfer gibt keine Warnung aus, wenn du einen ungültigen Typ (wie eine string) bereitstellst.
print(addLotsOfNumbers(1, 2, 3, 4, 5)) -- 15print(addLotsOfNumbers(1, 2, "car", 4, 5)) -- Versuch, einen String zu einer Zahl hinzuzufügen
Stattdessen weise dem ... einen Typ zu, genau wie du jedem anderen Typ eine Zuweisung machst:
local function addLotsOfNumbers(...: number)
Und jetzt wird die zweite Zeile einen Typfehler auslösen.
print(addLotsOfNumbers(1, 2, 3, 4, 5))print(addLotsOfNumbers(1, 2, "car", 4, 5)) -- Typfehler: string konnte nicht in Zahl umgewandelt werden
Dies funktioniert jedoch nicht, wenn du eine funktionale Typdefinition schreibst:
type addLotsOfNumbers = (...: number) -> number -- Erwarteter Typ, erhalten: ':'
Verwende stattdessen die Syntax ...type, um einen variadischen Typ zu definieren.
type addLotsOfNumbers = (...number) -> number
Vereinigungen und Schnittmengen
Du kannst sogar einen Typ als zwei oder mehr Typen mithilfe einer Vereinigung oder Schnittmenge definieren:
type numberOrString = number | stringtype type1 = {foo: string}type type2 = {bar: number}type type1and2 = type1 & type2 -- {foo: string} & {bar: number}local numString1: numberOrString = true -- Typfehlerlocal numString2: type1and2 = {foo = "hello", bar = 1}
Definiere einen inferierten Typ
Du kannst die Funktion typeof in einer Typdefinition für inferierte Typen verwenden:
type Car = typeof({Speed = 0,Wheels = 4}) --> Car: {Speed: number, Wheels: number}
Eine Möglichkeit, typeof zu verwenden, besteht darin, einen Metatyp mithilfe von setmetatable innerhalb der typeof-Funktion zu definieren:
type Vector = typeof(setmetatable({}::{x: number,y: number}, {}::{__add: (Vector, Vector|number) -> Vector}))-- Vector + Vector würde einen Vektor-Typ zurückgeben
Generika
Generika sind auf einer grundlegenden Ebene Parameter für Typen. Betrachte das folgende State-Objekt:
local State = {Key = "TimesClicked",Value = 0}
Ohne Generika würde der Typ für dieses Objekt wie folgt aussehen:
type State = {Key: string,Value: number}
Wenn du jedoch möchtest, dass der Typ für Value auf dem eingehenden Wert basiert, kommt hier die Generik ins Spiel:
type GenericType<T> = T
Das <T> bezeichnet einen Typ, der auf alles gesetzt werden kann. Am besten kann man sich das als einen Substitutionstyp vorstellen.
type List<T> = {T}local Names: List<string> = {"Bob", "Dan", "Mary"} -- Typ wird {string}local Fibonacci: List<number> = {1, 1, 2, 3, 5, 8, 13} -- Typ wird {number}
Generika können auch mehrere Substitutionen innerhalb der Klammern haben.
type Map<K, V> = {[K]: V}
Um das State-Objekt von früher so umzuarbeiten, dass es einen generischen Typ verwendet:
type State<T> = {Key: string,Value: T}
Funktionsgenerika
Funktionen können auch Generika verwenden. Das State-Beispiel leitet den Wert von T von den eingehenden Argumenten der Funktion ab.
Um eine generische Funktion zu definieren, füge <> zum Funktionsnamen hinzu:
local function State<T>(key: string, value: T): State<T>
return {
Key = key,
Value = value
}
end
local Activated = State("Activated", false) -- State<boolean>
local TimesClicked = State("TimesClicked", 0) -- State<number>
Typ-Exports
Um es so zu machen, dass ein Typ außerhalb eines ModuleScript verwendet werden kann, verwende das Schlüsselwort export:
Typenmodul in ReplicatedStorageexport type Cat = {Name: string,Meow: (Cat) -> ()}
Skript, das das Typenmodul verwendet
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Types = require(ReplicatedStorage.Types)
local newCat: Types.Cat = {
Name = "metatablecat",
Meow = function(self)
print(`{self.Name} sagte miau`)
end
}
newCat:Meow()