EMZETT.
Login

Kurz: Lua kennt keine Ausnahmeklassen: error löst einen Fehler aus, pcall (protected call) fängt ihn ab.

Teil des Kurses Lua

Kapitel 7 von 8 im Kurs Lua. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Fehlerbehandlung

Lua kennt keine Ausnahmeklassen: error löst einen Fehler aus, pcall (protected call) fängt ihn ab.

local function teile(a, b)
    if b == 0 then error("Division durch null") end
    return a / b
end
 
print(pcall(teile, 10, 2))
local ok, meldung = pcall(teile, 1, 0)
print(ok, meldung)
 
local ok2, fehler = pcall(function() local x = nil; return x.feld end)
print(ok2, fehler)
 
local ok3, tabelle = pcall(error, {code = 404, text = "nicht gefunden"})   -- beliebige Werte als Fehler
print(ok3, tabelle.code, tabelle.text)
 
print(select(2, pcall(error, "ohne Position", 0)))                         -- Level 0: ohne Quelltextposition
print(pcall(assert, false, "Assertion fehlgeschlagen"))
print(pcall(assert, 1 == 1, "nie sichtbar"))
 
local function mit_traceback()
    return xpcall(function() error("tief unten") end, function(m) return "behandelt: " .. m end)
end
print(mit_traceback())
 
local f, err = io.open("/gibt/es/nicht.txt", "r")      -- viele Funktionen liefern nil, Fehlertext
print(f, err)
print(tonumber("x"), tostring(nil))

Ausgabe:

true	5.0
false	main.lua:2: Division durch null
false	main.lua:10: attempt to index a nil value (local 'x')
false	404	nicht gefunden
ohne Position
false	Assertion fehlgeschlagen
true	true	nie sichtbar
false	behandelt: main.lua:21: tief unten
nil	/gibt/es/nicht.txt: No such file or directory
nil	nil

Konvention: Funktionen, die scheitern können, liefern entweder nil, "Fehlertext" oder lösen error aus.

Dateien

local pfad = os.tmpname()
local f = assert(io.open(pfad, "w"))
f:write("Zeile 1\n", "Zeile 2\n")
f:write(string.format("%s hat %d Punkte\n", "Mia", 120))
f:close()
 
for zeile in io.lines(pfad) do print(#zeile, zeile) end
 
local f2 = assert(io.open(pfad, "a"))
f2:write("Zeile 4\n")
f2:close()
 
local f3 = assert(io.open(pfad))
local inhalt = f3:read("a")        -- "a": alles
f3:close()
print(#inhalt, select(2, inhalt:gsub("\n", "")))
 
local f4 = assert(io.open(pfad))
print(f4:read("l"))                -- eine Zeile
print(f4:read("n"))                -- eine Zahl (nil, wenn keine)
f4:close()
os.remove(pfad)
print((io.open(pfad)))

Ausgabe:

7	Zeile 1
7	Zeile 2
18	Mia hat 120 Punkte
43	4
Zeile 1
nil
nil

Module

Ein Modul ist eine Datei, die eine Tabelle zurückgibt. require lädt sie genau einmal:

-- Datei mathe_helfer.lua
local M = {}
function M.quadrat(x) return x * x end
function M.summe(t) local s = 0 for _, v in ipairs(t) do s = s + v end return s end
M.VERSION = "1.0"
return M
 
-- In einer anderen Datei
-- local helfer = require("mathe_helfer")
-- print(helfer.quadrat(5))

Wir simulieren das, indem wir ein Modul in package.preload registrieren:

package.preload["mathe_helfer"] = function()
    local M = {}
    function M.quadrat(x) return x * x end
    M.VERSION = "1.0"
    return M
end
 
local helfer = require("mathe_helfer")
local nochmal = require("mathe_helfer")
print(helfer.quadrat(5), helfer.VERSION, helfer == nochmal)
print(package.loaded["mathe_helfer"] == helfer)
print(pcall(require, "gibt_es_nicht") == false)

Ausgabe:

25	1.0	true
true
true

Coroutinen

Eine Coroutine ist eine Funktion, die sich anhalten (yield) und später fortsetzen (resume) lässt. Das ermöglicht Generatoren, Zustandsmaschinen und kooperatives Multitasking:

local co = coroutine.create(function(a, b)
    print("Start mit", a, b)
    local c = coroutine.yield(a + b)
    print("fortgesetzt mit", c)
    local d, e = coroutine.yield(c * 2)
    print("nochmal", d, e)
    return "fertig"
end)
 
print(coroutine.resume(co, 1, 2))
print(coroutine.status(co))
print(coroutine.resume(co, 10))
print(coroutine.resume(co, "x", "y"))
print(coroutine.status(co))
print(coroutine.resume(co))
 
-- Generator mit wrap
local function zaehle(bis)
    return coroutine.wrap(function()
        for i = 1, bis do coroutine.yield(i) end
    end)
end
for n in zaehle(4) do io.write(n, " ") end
print()
 
local function fib()
    return coroutine.wrap(function()
        local a, b = 0, 1
        while true do coroutine.yield(a); a, b = b, a + b end
    end)
end
local g, liste = fib(), {}
for _ = 1, 10 do liste[#liste + 1] = g() end
print(table.concat(liste, " "))

Ausgabe:

Start mit	1	2
true	3
suspended
fortgesetzt mit	10
true	20
nochmal	x	y
true	fertig
dead
false	cannot resume dead coroutine
1 2 3 4
0 1 1 2 3 5 8 13 21 34

Kooperatives Multitasking

local aufgaben = {}
local function starte(name, schritte)
    aufgaben[#aufgaben + 1] = {name = name, co = coroutine.create(function()
        for i = 1, schritte do
            print(name .. " Schritt " .. i)
            coroutine.yield()
        end
    end)}
end
starte("A", 3); starte("B", 2)
 
while #aufgaben > 0 do
    for i = #aufgaben, 1, -1 do
        local t = aufgaben[i]
        coroutine.resume(t.co)
        if coroutine.status(t.co) == "dead" then table.remove(aufgaben, i) end
    end
end
print("alle fertig")

Ausgabe:

B Schritt 1
A Schritt 1
B Schritt 2
A Schritt 2
A Schritt 3
alle fertig

Merke

  • error, pcall, xpcall, assert für Fehler; Rückgabe nil, meldung als Konvention
  • Dateien: io.open, f:read, f:write, io.lines, immer close
  • Module: Datei gibt Tabelle zurück, require lädt einmal
  • Coroutinen: create/wrap, resume, yield, status

Übungsaufgabe

Schreibe einen Generator, der die Primzahlen bis 50 liefert, mit coroutine.wrap.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Metatabellen und Objektorientierung

Weiter: Einsatzgebiete, Ökosystem und Spickzettel

Alle Kapitel: Lua im Überblick