Grundlagen

Acht Dinge machen den größten Teil von Hilde aus. Jeder Abschnitt zeigt ein kurzes Programm aus dem Hilde-Repository, entweder eines der Beispiele oder ein Modul aus dem Fehlerkatalog des Compilers, und dazu die Ausgabe von hilde check. Alle Ausgaben auf dieser Seite stammen von hilde check, hilde compile und den gebauten Programmen. Nichts davon ist von Hand abgetippt. Der Compiler meldet sich auf Englisch.

1. Module, Funktionen, Records, Unions und case

Ein Modul besteht aus einer Schnittstelle, Name.hih, die sagt, was es anbietet, und einem Rumpf, Name.hiq, der den Code enthält. Das h in .hih steht für Header, wie in C; .hiq ist ein Wortspiel auf „high IQ“, und das q steht auch für Query. Jede öffentliche Funktion nennt in der Schnittstelle ihren Vertrag oder begründet in ihrem Docstring, warum sie keinen hat. Ein Programm beginnt bei der Funktion, die mit --entrypoint angegeben wird.

Datei: HelloWorld.hih

module Example.HelloWorld is
    """
    No contract: the exit code of the programme, the result of what it did.
    """
    function main(): ExitCode;
end module.

Datei: HelloWorld.hiq

module body Example.HelloWorld is
    function main(): ExitCode is
        printLn("Hello, world!");
        return ExitSuccess();
    end;
end module body.

Ausgabe von ./main (Exit-Status 0):

Hello, world!
Quelle: Beispiel examples/hello-world im Hilde-Repository, hilde 1.0.0

Ein Record fasst benannte Felder zusammen. Eine Union ist genau einer von mehreren Fällen, jeder mit eigenen Feldern. case zerlegt einen Wert anhand von Mustern, hier zwei Ebenen tief, mit Bereichen wie 97 .. 122 und mit Alternativen, die durch | verbunden sind. Deckt ein case nicht alle Werte ab, übersetzt das Programm nicht.

Datei: LearnMatch.hiq

module body Example.LearnMatch is
    -- A key of a keyboard: a character by its code, or one of two keys
    -- that are not characters.
    union Key: Free is
        case Char is
            code: Nat8;
        case Enter;
        case Escape;
    end;

    -- What a programme is told: a key was pressed, with or without shift,
    -- or some time has passed.
    union Event: Free is
        case Press is
            key: Key;
            shift: Bool;
        case Tick is
            millis: Nat32;
    end;

    -- The patterns look two levels deep: into the event and into its key.
    -- A range such as 97 .. 122 takes every value from 97 to 122, the
    -- small letters a to z.  A bar joins patterns that have the same
    -- answer, and an underscore takes any value without a name.  The first
    -- pattern that fits is taken, and the compiler refuses a case that
    -- leaves out a value.
    function describe(event: Event): Unit is
        case event of
            when Press(Char(97 .. 122), false) do
                printLn("a small letter");
            when Press(Char(97 .. 122), true) | Press(Char(65 .. 90), _) do
                printLn("a capital letter");
            when Press(Char(48 .. 57), _) do
                printLn("a digit");
            when Press(Char(_), _) do
                printLn("another character");
            when Press(Enter, _) | Press(Escape, _) do
                printLn("a key that is not a character");
            when Tick(0) do
                printLn("no time at all");
            when Tick(millis) do
                print("a tick of ");
                print(millis);
                printLn(" ms");
        end case;
        return nil;
    end;

    function press(code: Nat8, shift: Bool): Event is
        return Press(key => Char(code => code), shift => shift);
    end;

    function main(): ExitCode is
        describe(press('q', false));
        describe(press('q', true));
        describe(press('Q', false));
        describe(press('7', false));
        describe(press('!', true));
        describe(Press(key => Enter(), shift => false));
        let none: Nat32 := 0;
        let quarter: Nat32 := 250;
        describe(Tick(millis => none));
        describe(Tick(millis => quarter));
        return ExitSuccess();
    end;
end module body.

Ausgabe von ./main (Exit-Status 0):

a small letter
a capital letter
a capital letter
a digit
another character
a key that is not a character
no time at all
a tick of 250 ms
Quelle: Beispiel examples/learn-match im Hilde-Repository, hilde 1.0.0

Jeder Typ ist entweder Free oder Linear. Ein Free-Wert darf kopiert werden, ein Linear-Wert muss genau einmal verwendet werden. Die Unions oben sind Free. Hilde kennt keine Operatorrangfolge: a + b * c muss man als a + (b * c) schreiben. Fehlen die Klammern, lehnt hilde check die Zeile ab und schlägt die Korrektur vor:

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Parse Error
  Error code: H0009
  Module:
    [unknown]
  Location:
    Filename: 'Example.hiq'
    From: line 3, column 34
    To: line 3, column 35
  Description:
    Hilde has no operator precedence: an operator takes two operands, and only `+`, `*`, `and` and `or` can be chained with themselves. Put the operation before `*` in parentheses, as in `(a + b) * c`.
    
    Suggested fix: `(1 + 2)`
  Code:
    1 | module body Example is
    2 |     function main(): ExitCode is
    3 |         let total: Int32 := 1 + 2 * 3;
    4 |         printLn(total);
    5 |         return ExitSuccess();
Quelle: Fehlerkatalog von Hilde, H0009, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H0005 (die Tokens ergeben kein gültiges Programm, oft zwei Operatoren ohne Klammern), H0009 (ein Operator folgt ohne Klammern auf eine andere Operation).

Namen

Für Typen, Typklassen und Funktionen übernimmt Hilde die Schreibweise von Austral (Case Convention); bei Modulen, Variablen und Konstanten weicht die Standardbibliothek von Hilde ab:

Zwei Regeln setzt der Compiler durch: In einem Muster ist ein Name mit kleinem Anfangsbuchstaben eine Variable und einer mit großem eine Variante (H3087). In einer Testdatei ist jede Funktion, deren Name aus test und einem Großbuchstaben oder einer Ziffer besteht, ein Test, etwa testLength.

2. Referenzen und Ausleihen

Eine Funktion, die einen Wert nur liest, bekommt eine Referenz &[T, R]. Eine Funktion, die ihn ändert, bekommt eine Schreibreferenz &![T, R]. R ist die Region, also der Abschnitt des Codes, in dem die Referenz gilt. &x und &!x leihen eine Variable für einen einzelnen Aufruf aus, borrow ... do ... end borrow für einen ganzen Block. Solange ein Wert ausgeliehen ist, kann er nicht verbraucht werden.

Datei: LearnBorrow.hiq

module body Example.LearnBorrow is
    record Counter: Linear is
        count: Nat32;
    end;

    -- A write reference, &!, lets the callee change the caller's value.
    generic [R: Region]
    function bump(counter: &![Counter, R]): Unit is
        counter->count := counter->count + 1;
        return nil;
    end;

    -- A write reference passed on is used up.  &~ passes on a new one
    -- and keeps the old one usable; the last use may pass it on itself.
    generic [R: Region]
    function bumpTwice(counter: &![Counter, R]): Unit is
        bump(&~counter);
        bump(counter);
        return nil;
    end;

    function main(): ExitCode is
        -- var, because a write reference is taken from it.
        var counter: Counter := Counter(count => 0);
        bumpTwice(&!counter);
        bump(&!counter);
        let { count: Nat32 } := counter;
        printLn(count);
        return ExitSuccess();
    end;
end module body.

Ausgabe von ./main (Exit-Status 0):

3
Quelle: Beispiel examples/learn-borrow im Hilde-Repository, hilde 1.0.0

Wer eine Variable verbraucht, während sie ausgeliehen ist, bekommt einen Fehler:

Datei: Example.hiq

module body Example is
    record Ticket: Linear is
        number: Int32;
    end;

    function redeem(ticket: Ticket): Int32 is
        let { number: Int32 } := ticket;
        return number;
    end;

    function main(): ExitCode is
        let ticket: Ticket := Ticket(number => 7);
        borrow view: &[Ticket, R] := &ticket do
            printLn(redeem(ticket));
        end borrow;
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Linearity Error
  Error code: H4006
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 14, column 12
    To: line 14, column 36
  Description:
    The variable `ticket` cannot be consumed while it is borrowed (immutably).
  Code:
    12 |         let ticket: Ticket := Ticket(number => 7);
    13 |         borrow view: &[Ticket, R] := &ticket do
    14 |             printLn(redeem(ticket));
    15 |         end borrow;
    16 |         return ExitSuccess();
Quelle: Fehlerkatalog von Hilde, H4006, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H4006 (verbraucht, während sie zum Lesen ausgeliehen ist), H4007 (verbraucht, während sie zum Schreiben ausgeliehen ist), H4011 (ausgeliehen, nachdem sie schon verbraucht wurde).

3. Regionen

Eine Region legt fest, wie lange eine Referenz gilt. Eine borrow-Anweisung öffnet eine eigene Region und nennt sie im Typ der Referenz, etwa rho in &[Index, rho]. &x in einem Aufruf leiht nur für diesen einen Aufruf aus. Eine generische Funktion deklariert einen Regionsparameter mit generic [R: Region]. Diesen wählt der Aufrufer, und er lebt länger als der Aufruf. Die Funktion darf deshalb eine Referenz in R zurückgeben, die sie selbst bekommen hat, aber nie eine Referenz auf eine eigene lokale Variable, deren Region innerhalb der Funktion endet.

Datei: Example.hiq

module body Example is
    generic [R: Region]
    function same(x: &[Int32, R]): &[Int32, R] is
        return x;
    end;

    function main(): ExitCode is
        let n: Int32 := 5;
        let value: Int32 := !same(&n);
        printLn(value);
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 0):

hilde check: 1 modules given, 1 checked, no error

Ausgabe von ./main (Exit-Status 0):

5
Quelle: Fehlerkatalog von Hilde, H3094, richtige Form, hilde 1.0.0

Gibt eine Funktion eine Referenz auf eine lokale Variable als R heraus, wird das abgelehnt:

Datei: Example.hiq

module body Example is
    generic [R: Region]
    function local(n: Int32): &[Int32, R] is
        let x: Int32 := n;
        return &x;
    end;

    function main(): ExitCode is
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Type Error
  Error code: H3094
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 5, column 8
    To: line 5, column 18
  Description:
    A reference made inside this function cannot be handed out as `R`.
    
    `R` is a region parameter of the function: the caller chooses it, and it outlives the call.  The reference here has the region `Region<55>`, which ends inside the function.
  Code:
    3 |     function local(n: Int32): &[Int32, R] is
    4 |         let x: Int32 := n;
    5 |         return &x;
    6 |     end;
    7 | 
Quelle: Fehlerkatalog von Hilde, H3094, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H3094 (Referenz auf eine lokale Variable in einer Region zurückgegeben, die der Aufrufer wählt), H6025 (zwei verschachtelte borrow-Anweisungen geben ihren Regionen denselben Namen), H3026 (Schreibreferenz in eine Lesereferenz mit anderem Typ oder anderer Region umgewandelt).

4. Lineare Typen: Free und Linear

Ein Linear-Wert hält etwas, das ordentlich beendet werden muss: eine Datei, eine Verbindung, einen Puffer, ein Ticket. Der Compiler verfolgt jede lineare Variable durch alle Pfade einer Funktion. Sie muss genau einmal verbraucht werden, entweder indem man sie an eine Funktion übergibt, die sie übernimmt, oder indem man sie mit let { ... } := zerlegt.

Datei: Example.hiq

module body Example is
    record Ticket: Linear is
        number: Int32;
    end;

    function main(): ExitCode is
        let ticket: Ticket := Ticket(number => 7);
        let { number: Int32 } := ticket;
        printLn(number);
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 0):

hilde check: 1 modules given, 1 checked, no error

Ausgabe von ./main (Exit-Status 0):

7
Quelle: Fehlerkatalog von Hilde, H4012, richtige Form, hilde 1.0.0

Vergisst man sie, ist das ein Fehler:

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Linearity Error
  Error code: H4012
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 8, column 8
    To: line 8, column 29
  Description:
    The variable `ticket` is not consumed by the time of the return statement. Did you forget to call a destructor, or destructure the contents?
  Code:
     6 |     function main(): ExitCode is
     7 |         let ticket: Ticket := Ticket(number => 7);
     8 |         return ExitSuccess();
     9 |     end;
    10 | end module body.
Quelle: Fehlerkatalog von Hilde, H4012, fehlerhaftes Modul, hilde 1.0.0

Verwendet man sie zweimal, ebenfalls:

Datei: Example.hiq

module body Example is
    record Ticket: Linear is
        number: Int32;
    end;

    function redeem(ticket: Ticket): Int32 is
        let { number: Int32 } := ticket;
        return number;
    end;

    function main(): ExitCode is
        let ticket: Ticket := Ticket(number => 7);
        printLn(redeem(ticket));
        printLn(redeem(ticket));
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Linearity Error
  Error code: H4008
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 14, column 8
    To: line 14, column 32
  Description:
    The variable `ticket` has already been consumed.
  Code:
    12 |         let ticket: Ticket := Ticket(number => 7);
    13 |         printLn(redeem(ticket));
    14 |         printLn(redeem(ticket));
    15 |         return ExitSuccess();
    16 |     end;
Quelle: Fehlerkatalog von Hilde, H4008, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H4012 (bei der Rückkehr nicht verbraucht), H4008 (ein zweites Mal verbraucht), H4005 (in einem Ausdruck zweimal verbraucht), H4004 (die Zweige behandeln sie unterschiedlich), H4001 (ein Free-Record enthält ein Linear-Feld).

5. Verträge: requires und ensures

Ein Vertrag sagt, was beim Aufruf einer Funktion gelten muss (requires) und was bei ihrer Rückkehr gilt (ensures). Bei einer öffentlichen Funktion steht der Vertrag in der Schnittstelle, direkt bei der Funktion. Hilde prüft Verträge immer, während das Programm läuft. Außerdem erzeugt hilde test daraus Zufallstests, und einfache Fälle beweist der Compiler schon beim Übersetzen.

Datei: LearnContracts.hih

module Example.LearnContracts is
    -- An account holds a balance.  It is linear: it is opened once and
    -- closed once, and nothing else can make one disappear.
    record Account: Linear is
        balance: Nat64;
    end;

    """
    No contract: a value of its own type built from its arguments; no property cheaper than its type holds.
    """
    function open(amount: Nat64): Account;

    """
    No contract: a number read from its argument; its type gives its range, and nothing cheaper holds.
    """
    generic [R: Region]
    function balance(account: &[Account, R]): Nat64;

    -- A deposit adds a positive amount, and the balance afterwards is the
    -- old balance plus the amount.
    generic [R: Region]
    function deposit(account: &![Account, R], amount: Nat64): Unit
        requires amount > 0
        ensures balance(&account) = (old(balance(&account)) + amount);

    -- A withdrawal takes a positive amount the account holds.  The caller
    -- has to make sure of that: a call that does not is stopped before the
    -- function runs, and the message names this line and the call.
    generic [R: Region]
    function withdraw(account: &![Account, R], amount: Nat64): Unit
        requires (amount > 0) and (amount <= balance(&account))
        ensures balance(&account) = (old(balance(&account)) - amount);

    """
    Closes the handle it is given.
    No contract: it only frees or closes what it is given.
    """
    function close(account: Account): Nat64;

    """
    No contract: the exit code of the programme, the result of what it did.
    """
    function main(): ExitCode;
end module.

Datei: LearnContractsBroken.hiq

import Example.LearnContracts (
    Account,
    open,
    balance,
    withdraw,
    close
);

module body Example.LearnContractsBroken is
    -- The second withdrawal asks for more than the account holds.  The
    -- requires of withdraw does not hold, so the programme stops before
    -- withdraw runs, and the message names the contract in
    -- LearnContracts.hih and this call.  The last two lines never run.
    function main(): ExitCode is
        var account: Account := open(100);
        withdraw(&!account, 30);
        printLn(balance(&account));
        withdraw(&!account, 71);
        printLn(balance(&account));
        printLn(close(account));
        return ExitSuccess();
    end;
end module body.
Quelle: Beispiel examples/learn-contracts im Hilde-Repository, hilde 1.0.0

Das Programm main hält jeden Vertrag ein. broken will mehr abheben, als auf dem Konto ist, und hält an, bevor withdraw überhaupt läuft:

Ausgabe von ./main (Exit-Status 0):

150
30
30

Ausgabe von ./broken (Exit-Status 255):

70
Hilde: contract violated: the requires (amount > 0) and (amount <= balance(&account)) of Example.LearnContracts.withdraw (LearnContracts.hih:38) does not hold for the call at LearnContractsBroken.hiq:25; the caller is at fault
Quelle: Beispiel examples/learn-contracts im Hilde-Repository, hilde 1.0.0

Steht der Vertrag einer öffentlichen Funktion im Rumpf statt in der Schnittstelle, wird er abgelehnt:

Ausgabe von hilde check Example.hih,Example.hiq (Exit-Status 255):

Error:
  Title: Declaration Error
  Error code: H2051
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 3, column 8
    To: line 3, column 22
  Description:
    The body of the public function `half` holds a contract.
    
    The contract of a public function stands in its interface only: it is written at `Example.hih:3`.  Remove the requires, ensures and raises clauses from the body.
  Code:
    1 | module body Example is
    2 |     function half(n: Nat32): Nat32
    3 |         requires n > 0
    4 |     is
    5 |         return n / 2;
Quelle: Fehlerkatalog von Hilde, H2051, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H2051 (Vertrag im Rumpf einer öffentlichen Funktion) und die weiteren Codes im Abschnitt zu Verträgen.

Verträge lassen sich nicht abschalten

In C++26 (P2900R14) wird eine Vertragsprüfung mit einer von vier Semantiken ausgewertet: ignore, observe, enforce oder quick-enforce. Welche gilt, legt die Implementierung fest; P2900 empfiehlt eine Option, mit der alle Prüfungen ignoriert werden, und eine, mit der alle durchgesetzt werden. Eine Prüfung lässt sich also abschalten. Die Semantik wird je Auswertung gewählt: Ein Programm darf dieselbe Funktion in einer Übersetzungseinheit mit durchgesetzten und in einer anderen mit ignorierten Verträgen übersetzen; welche Fassung einer inline-Funktion dann läuft, ist ohne Änderungen am Linker nach P2900R14 „effectively random“ (Abschnitt 3.5.11 „Mixed Mode“). Der rumänische Kommentar RO 2-056 und das Papier P3911R2 „Make Contracts Reliably Non-Ignorable“ schlagen Verträge vor, die sich nicht ignorieren lassen.

Hilde legt fest: Ein Vertrag ist ein Vertrag. Einfache Fälle beweist der Compiler beim Übersetzen, alles andere prüft das Programm zur Laufzeit. Eine Option, die das abschaltet, gibt es nicht. Das kostet Laufzeit; Verträge sind deshalb kein Allheilmittel, sondern werden in der richtigen Dosis eingesetzt. Eine öffentliche Funktion ohne Vertrag sagt, warum: No contract: <Grund>. Der Vertrag einer öffentlichen Funktion steht immer in ihrer Schnittstelle (.hih), sichtbar und verbindlich für jeden Aufrufer; der Compiler weist einen Vertrag im Rumpf ab (H2051).

6. Fehlerbehandlung

Eine Funktion, die scheitern kann, gibt einen Option-Wert zurück (ein Wert oder keiner) oder einen Either-Wert (ein Wert oder ein Grund). Der Operator ? nach einem Aufruf reicht ein Scheitern sofort an den eigenen Aufrufer weiter. Wird ein Option- oder Either-Wert nicht ausgewertet, ist das ein Übersetzungsfehler. Ein Ergebnis, auf das es wirklich nicht ankommt, verwirft man ausdrücklich mit ignore. Eine Funktion kann ihre Fehlerarten auch mit raises in ihrer Signatur angeben.

Datei: LearnErrors.hiq

module body Example.LearnErrors is
    -- Half of an even number, and nothing for an odd one.
    function halve(n: Int32): Option[Int32] is
        if (n - ((n / 2) * 2)) = 0 then
            let half: Int32 := n / 2;
            let some: Option[Int32] := Some(value => half);
            return some;
        else
            let none: Option[Int32] := None();
            return none;
        end if;
    end;

    -- ? after a call, as the whole right side of a let: a None makes
    -- this function return None at once, a Some gives its value.
    function eighth(n: Int32): Option[Int32] is
        let a: Int32 := halve(n)?;
        let b: Int32 := halve(a)?;
        let c: Int32 := halve(b)?;
        let some: Option[Int32] := Some(value => c);
        return some;
    end;

    function show(result: Option[Int32]): Unit is
        case result of
            when Some(value as v: Int32) do
                printLn(v);
            when None do
                printLn("odd on the way");
        end case;
        return nil;
    end;

    function main(): ExitCode is
        show(eighth(40));
        show(eighth(12));
        -- An Option or Either nobody looks at is a compile error; a
        -- result that really does not matter is thrown away by name.
        ignore(halve(7));
        return ExitSuccess();
    end;
end module body.

Ausgabe von ./main (Exit-Status 0):

5
odd on the way
Quelle: Beispiel examples/learn-errors im Hilde-Repository, hilde 1.0.0

Ein Ergebnis stillschweigend wegzuwerfen wird abgelehnt:

Datei: Example.hiq

module body Example is
    function half(n: Int32): Option[Int32] is
        let r: Option[Int32] := Some(value => n / 2);
        return r;
    end;

    function main(): ExitCode is
        half(4);
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Type Error
  Error code: H3037
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 8, column 8
    To: line 8, column 16
  Description:
    The value of this expression has type `Option[Int32]`, which carries a failure the caller is meant to look at, so it may not be thrown away. Handle it with a case statement, hand it to your own caller with the ? operator, or, if the failure really is to be ignored here, say so in the program with ignore(...).
  Code:
     6 | 
     7 |     function main(): ExitCode is
     8 |         half(4);
     9 |         return ExitSuccess();
    10 |     end;
Quelle: Fehlerkatalog von Hilde, H3037, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H3037 (Option- oder Either-Wert verworfen), H3038 (? auf einem Wert, der keins von beiden ist), H3039 (? in einer Funktion, die das Scheitern nicht weitergeben kann).

7. unsafe nur in den Bindings

Hilde hat keine unsafe-Blöcke. Ein Modul ist entweder als Ganzes unsafe oder gar nicht. Nur ein Modul mit pragma Unsafe_Module; darf eine C-Funktion einbinden, C mit @embed einfügen oder Hilde.Memory importieren, und der Compiler prüft das. In der Standardbibliothek sind das die dünnen Bindings unter Bindings.*. Ein Programm importiert stattdessen das sichere Modul, das darauf aufbaut.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Declaration Error
  Error code: H2028
  Module:
    Example
  Location:
    [no span available]
  Description:
    Cannot import from the `Hilde.Memory` module in a safe module.
    
    Consider using a safe wrapper or adding `pragma Unsafe_Module;`
  Code:
    [no span available]
Quelle: Fehlerkatalog von Hilde, H2028, fehlerhaftes Modul, hilde 1.0.0

Datei: Example.hiq

import Bindings.Time (wallNanos);

module body Example is
    function main(): ExitCode is
        printLn(wallNanos() > 0);
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Declaration Error
  Error code: H2045
  Module:
    Example
  Location:
    [no span available]
  Description:
    `Example` imports from `Bindings.Time`, a module that binds C.
    
    Only the standard library imports a module whose name begins with `Bindings.`; a program imports the safe module built on it, whose name begins with `Standard.`.
  Code:
    [no span available]
Quelle: Fehlerkatalog von Hilde, H2045, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H2028 (Hilde.Memory in einem sicheren Modul), H2038 (@embed in einem sicheren Modul), H2045 (ein Programm importiert ein Bindings.*-Modul). Mehr auf der Seite Sicherheit.

8. Tasks und Kanäle

Nebenläufigkeit ist in Hilde strukturiert. Ein Task wird in einem Scope gestartet, und closeScope wartet, bis alle Tasks des Scopes fertig sind. Ein Task ist ein linearer Wert und muss deshalb wieder eingesammelt werden. spawn übernimmt sein Argument, der Task besitzt also alles, woran er arbeitet.

Datei: Example.hiq

import Standard.Task (Scope, openScope, closeScope, Task, spawn, join);

module body Example is
    function double(n: Index): Index is
        return n * 2;
    end;

    function main(): ExitCode is
        let n: Index := 5;
        case openScope() of
            when Some(value as opened: Scope) do
                var scope: Scope := opened;
                let work: Fn[Index, Index] := double;
                case spawn(&!scope, work, n) of
                    when Right(right as task: Task[Index]) do
                        printLn(join(task));
                    when Left(left as back: Index) do
                        printLn(back);
                end case;
                closeScope(scope);
            when None do
                skip;
        end case;
        return ExitSuccess();
    end;
end module body.

Ausgabe von hilde check Example.hiq (Exit-Status 0):

hilde check: 1 modules given, 8 checked, no error

Ausgabe von ./main (Exit-Status 0):

10
Quelle: Fehlerkatalog von Hilde, H3002, richtige Form, hilde 1.0.0

Ein Task darf keine Referenz übernehmen, weil er länger leben könnte als die Ausleihe, aus der die Referenz stammt. In diesem Beispiel lässt hilde check das Modul noch durch, erst hilde compile lehnt es ab. Das Argument eines Tasks wird nämlich erst geprüft, wenn generischer Code instanziiert wird, und das macht hilde check nicht:

Ausgabe von hilde check Example.hiq (Exit-Status 0):

hilde check: 1 modules given, 8 checked, no error

Ausgabe von hilde compile Example.hiq --entrypoint=Example:main --output=main (Exit-Status 255):

Error:
  Title: Type Error
  Error code: H3002
  Module:
    Example
  Location:
    [no span available]
  Description:
    The argument of a task holds a reference or a span.
    
    A task can outlive the borrow the reference comes from, so what is handed to `Standard.Task.spawn` must own everything it points at.  Move the value in, or copy what the task needs.
  Code:
    [no span available]
Quelle: Fehlerkatalog von Hilde, H3002, fehlerhaftes Modul, hilde 1.0.0

Einem Task in einem leihenden Scope, gestartet mit spawnLending, darf man nur leihen, was der Scope selbst ausgeliehen hat. Eine Referenz aus einer anderen Region wird abgelehnt, und das findet schon hilde check:

Ausgabe von hilde check Example.hiq (Exit-Status 255):

Error:
  Title: Type Error
  Error code: H3066
  Module:
    Example
  Location:
    Filename: 'Example.hiq'
    From: line 18, column 24
    To: line 23, column 33
  Description:
    The argument of a task in a lending scope holds a reference of another region.
    
    A task started with `Standard.Task.spawnLending` may be lent only what the scope's own borrow lends -- the region the scope was opened for -- because only that borrow is sure to outlive the task.
  Code:
    16 |                     borrow i: &[Index, sigma] := &inner do
    17 |                         let work: Fn[&[Index, sigma], Index] := read;
    18 |                         case spawnLending(&!scope, work, i) of
    19 |                             when Right(right as task: Task[Index]) do
    20 |                                 printLn(join(task));
    21 |                             when Left(left as back: &[Index, sigma]) do
    22 |                                 printLn(0);
    23 |                         end case;
    24 |                     end borrow;
    25 |                     closeLending(scope);
Quelle: Fehlerkatalog von Hilde, H3066, fehlerhaftes Modul, hilde 1.0.0

Typische Codes: H3002 (Referenz oder Span an einen Task übergeben), H3066 (Referenz aus einer anderen Region an einen Task in einem leihenden Scope übergeben). Kanäle, bei denen Sender und Empfänger jeweils lineare Werte sind, und das Warten auf zwei Kanäle gleichzeitig bietet Standard.Task. Mutexe und atomare Werte stehen in Standard.Shared, Abbruch und Fristen in Standard.Context. Die Tour zeigt vier Tasks in einem Scope.