Try/exceptions

Unlike PHP, in THP all errors must be explicitly declared and handled.

Declare that a function returns an exception

Possible errors have their own syntax: Type!Error. This means: This may be a Type, or an Error.

For example, a function that returned a DivisionByZero may be written like this:


fun invert(Int number) -> Int!DivisionByZero {
╰╴No statement matched
if number == 0 { throw DivisionByZero() } return 1 / number }
thp

In the previous segment, Int!DivisionByZero denotates that the function may return either a DivisionByZero error or an Int.

The throw keyword is used to denotate that an error is being returned.

Multiple error returns

TODO: properly define syntax, how this interacts with type unions.

Multiple errors are chained with !.


fun sample() -> Int!Error1!Error2!Error3 { 
╰╴No statement matched
/* ... */ }
thp

Error handling

The caller must handle all possible errors, they don’t automatically bubble up the stack.

THP provides syntax for handling errors following certain patterns, via try expressions:

Naked try

Use a naked try when you want to rethrow an error, if there is any.


  fun dangerous() -> Int!Exception {   // May throw randomly
╰╴No statement matched
return if Math.random() < 0.5 { 50 } else { Exception("Unlucky") } } fun run() -> Void!Exception { // If `dangerous()` throws, the function exits with the same error. // Otherwise, continues val result = try dangerous() print("The result is {result}") } val res1 = run() // First, without error val res2 = run() // Then, an example with error
thp

In the previous example:

Try/return

Try/return will return a new value if an expression fails, otherwise will assign the success value and continue.

Try/return will run a function and assign its value if Ok is found. Otherwise, it will return a new value specified by the programmer.


fun run() -> Int
╰╴No statement matched
{ val result = try dangerous() return 0 // ... }
thp

In the previous example:

Try/else

Try/else will assign a new value if an expression fails.


  fun run(Exception!Int possible_value)
╰╴No statement matched
{ val mid = try possible_value else 666 print("The value is: {mid}") } run(777) // With an actual value run(Exception("oh uh")) // With an exception
thp

Either way, the function will continue executing.

Try/catch

Try/catch allows the error to be manually used & handled.


fun run()
╰╴No statement matched
{ val result = try dangerous() catch Exception e { // This is run if `dangerous()` throws. // `e` is the thrown error // Handle the error // ... // Return a new value to be assigned to `result` 0 } }
thp

A try/catch may have many catch clauses:


try dangerous()
╰╴No statement matched
catch Exception1 e {...} catch Exception2 e {...} catch Exception3 e {...}
thp