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 {
  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 { 
  /* ... */
}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
    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 errorthp

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
{
  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)
{
    val mid = try possible_value else 666

    print("The value is: {mid}")
}

run(777)                // With an actual value
run(Exception("oh uh")) // With an exceptionthp

Either way, the function will continue executing.

Try/catch

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

fun run()
{
  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()
catch Exception1 e
{...}
catch Exception2 e
{...}
catch Exception3 e
{...}thp