Brussels / 1 & 2 February 2025

schedule

Effects Everywhere: Error Handling and Design-By-Contract in Fuzion


This talk presents advances in the Fuzion languages focusing on effect handlers used to implement Fuzion's Design-by-Contract mechanism inspired by Betrand Meyer's Eiffel language. The use of effect handlers for runtime checks gives a powerful means to handle failures at runtime and to create code that is robust in case of programming errors that would otherwise result in a crash. I will dive into the Fuzion effect mechanism and explain how this it is used to implement design-by-contract as pure syntax sugar in a way that permits error handling at runtime. While doing this, some new fun aspects of Fuzion like free types or partial application will be presented. The talk will use live demos of the presented mechanisms.

Introduction

Fuzion is a new functional and object-oriented language built on the universal concept of a Fuzion feature, a generalization of a pure function and a class. Effect handlers are used to model non-functional aspects. The principle of Design-by-Contract with pre- and post-conditions is used to formally document the requirements and guarantees of features in a way accessible to static analysis and runtime checks.

Fuzion Language Overview

Fuzion is a modern general purpose programming language that unifies concepts found in structured, functional and object-oriented programming languages into the concept of a Fuzion feature. It combines a powerful syntax and safety features based on the design-by-contract principle with a simple intermediate representation that enables powerful optimizing compilers and static analysis tools to verify correctness aspects.

Fuzion was influenced by many other languages including Java, Python, Eiffel, Rust, Go, Lua, Kotlin, C#, F#, Nim, Julia, Clojure, C/C++, and many more. The goal of Fuzion is to define a language that has the expressive power present in these languages and allow high-performance implementations and powerful analysis tools. Furthermore, Fuzion addresses requirements for safety-critical applications by adding support for contracts that enable formal specification and enable detailed control over run-time checks.

Many current programming languages are getting more and more overloaded with new concepts and syntax to solve particular development or performance issues. Languages like Java/C# provide classes, interfaces, methods, packages, anonymous inner classes, local variables, fields, closures, etc. And these languages are currently further extended by the introductions of records/structs, value types, etc. The possibility of nesting these different concepts results in complexity for the developer and the tools (compilers, VMs) that process and execute the code.

For example, the possibility to access a local variable as part of the closure of a lambda expression may result in the compiler allocating heap space to hold the contents of that local variable. Hence, the developer has lost control over the allocation decisions made by the compiler.

In Fuzion, the concepts of classes, interfaces, methods, packages, fields and local variables are unified in the concept of a Fuzion feature. The decision where to allocate the memory associated with a feature (on the heap, the stack or in a register) is left to the compiler just as well as the decision if dynamic type information is needed. The developer is left with the single concept of a feature, the language implementation takes care of all the rest.

Error Handling using Effects

Faults are a run time manifestation of program errors (bugs). Typical language mechanisms used to cause for runtime faults are functions assert or panic or exceptions like RuntimeException. Here, I will show how this is done in Fuzion using design-by-contract and effects.

Introductory Example

Consider we are writing a feature that produces a string of the form 9² = 81 for any given numeric value, we can use this code

square(x N : numeric) => "{x}² = {x*x}"

[side note: N is a free type, so the feature square is defined for any type that matches the given constraint numeric. Free types are syntax sugar for explicit type parameters as in

square(N type : numeric, x N) => "{x}² = {x*x}"

Alternatively, an implementation for a specific type, e.g., u32, would look like this

square(x u32) => "{x}² = {x*x}"

side note end]

However, this will crash somewhere in the calculation of x*x in case of an overflow, we would like to handle this case and create an error, one solution is to add error handling using panic:

square(x N : numeric) ! panic =>
  if x *! x                         # *! checks if * operation would succeed without an overflow
    "{x}² = {x*x}"
  else
    panic "overflow for $x"

square (u8 30)

Here, x *! x checks if the multiplication can be performed without causing an overflow, such that we can avoid the error and call panic explicitly.

This is, however, a little ugly since it is not clear whose fault it is if this panic would occur, is the implementation of square faulty or is the caller to blame?

Using a pre-conditions documents the requirement on the argument x clearly in the signature of the feature and puts the blame on the caller:

square(x N : numeric)
pre
  debug: x *! x
=>
  "{x}² = {x*x}"

fallible effect

Fuzion provides an abstract effect fallible with inner feature try that takes a nullary lambda argument and produces a result on with inner feature catch that takes a lambda to be executed in case for a fault. It can be used as follows

FALLIBLE
  .try ()->
    ... code that may call FALLIBLE.cause to produce an error ...
  .catch e->
    ... code that handles fault with error `e` ...

The idea is that any effect that may fail due to some error would inherit from fallible such that there is a common way to handle this effect.

One example is the panic effect, we can now handle a panic as follows

panic.try ()->
    say (square 1000000)
  .catch s
    say "square panicked: $s"

If not run within an explicit panic.try, the default panic handler will be invoked which propagate the panic to fuzion.runtime.fault.

fallible hierarchy

Similar to Java using the class inheritance mechanism to create a hierarchy of Throwable exceptions, we would like to have a hierarchy of fallible effects. In Fuzion, this is done via default handlers that propagate to a more generic fallible, where fuzion.runtime.fault is the most generic one.

pre and post-conditions as effects

Fuzion defines a hierarchy of fallibleeffects as follows

                   fuzion.runtime.fault
                              A
                              |
                   +----------+--------------+
                   |                         |
       fuzion.runtime.contract_fault       panic
                   |
          +--------+-----------------+
          |                          |
fuzion.runtime.pre_fault    fuzion.runtime.post_fault

Pre- and post-conditions in Fuzion source code are essentially syntax sugar for code using the corresponding pre_fault and post_fault effects. The example above is de-sugared to code like this:

pre_square(x N : numeric) =>
  if !(debug: x *! x)
    fuzion.runtime.pre_fault.env.cause "debug: x *! x"

pre_and_square(x N : numeric) =>
  pre_square x
  square x

square(x N : numeric) =>
  "{x}² = {x*x}"

pre_and_square (u8 30)

Programmatic handling of faults

Now, it is possible to install handlers for a particular fallible at given points in the hierarchy, e.g., for pre_fault for preconditions only, contract_fault to handle pre_fault and post_fault, or the topmost fault to handle pre_fault, post_fault, panic and all other fallible that map to fault.

Type constraints

Using our square example with pre-condition, we see that the code does not work as expected for a float overflow:

say (square 3.2E200)

produces

3.2E200² = Infinity

instead of reporting an overflow. Using type constraints, we can specialize the code for specific type parameter values. In this example, we can extend our code to handle float differently in the pre-condition and disallow N.infinity as the result of squaring:

square(x N : numeric) ! panic
  pre
    debug: x *! x
    debug: (if N : float then x*x != N.infinity else true)
=>
  "{x}² = {x*x}"

Here, the N : float in the second precondition provides specific code for float types. The code in the following then clause sees N and all values of type N with the type constraint float, so it is possible to, e.g., access N.infinity, which is a type feature defined for float only.

Type constraints like N : float are compile time constants since a copy of square will be created for each actual type N, so these can be optimized away for all other types.

Such type constraints are extremely useful. E.g., Fuzion's base library feature Sequence contains a sort feature as follows

public sort
pre
  T : property.orderable
=>
  sort_by (<=)

the precondition ensures that sort will only be called if the element type T of the Sequence defines an order, which permits using `<='.

[side note: partial application comes to our help here. Without, the code would look like this

  sort_by (a,b -> a <= b)

]

Conclusion and Next Steps

The implementation of Design-by-Contract using effect handlers is another step to simplify the language implementation by degrading this mechanism to syntax sugar for code using effects. At the same time, this makes the language more powerful by enabling code to handle failures at runtime.

A small team of developers is working on bringing Fuzion ahead. The main focus of the work at the moment are

  • first real-world applications
  • a powerful standard library
  • foreign language interfaces for C and Java
  • improving static analyzers

Main points that are missing right now are

  • additional library modules for all sorts of application needs
  • better code optimization like inlining, specialization
  • highly optimizing back-ends
  • documentation, tutorials
  • more enthusiastic contributors and users!

Please feel free to contact me in case you want to use Fuzion or want to help making it a success!

Links

Fuzion portal website: https://fuzion-lang.dev Fuzion Sources on GitHub: https://github.com/tokiwa-software/fuzion

Speakers

Photo of Fridtjof Siebert Fridtjof Siebert

Links