Fuzion Logo
flang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Type Casting References

There are basically two kinds of type casts: First, those that change the static type of an expression of reference type (implemented in Java by the checkcast bytecode with the possibility to throw a runtime exception), that do not change the value itself but the way it is handled by the compiler, second, type conversions such as (float) Math.PI that may change the value they operate on (as to a lower precision value in this case).

Casting References

The need to cast references could be considered a fault in the applications design. Take this example (mix between Fuzion and Java):

  Person ref
  {
    abstract name string;
  }

  Student(redefine name string, id i64) : Person
  {
    study
    {
      ..
    }
  }

  Professor(redefine name string, employeeId i32) : Person
  {
    teach
    {
      ..
    }
  }

  printWithIds(l List<Person>)
  {
    for (p : l)
    {
      if (p instanceof Student)
      {
        stdout.println(p.name + " id " + ((Student) p).id));
      }
      if (p instanceof Professor)
      {
        stdout.println(p.name + " employee id " + ((Professor) p).employeeId));
      }
    }
  }

This code is inherently dangerous to change: Adding a new person kind, say,

  Assistant(redefine name string, tempId i32) : Person
  {
    work
    {
      ..
    }
  }

may cause this code to fail since Assitant is a case not handled by printWithIds and the compiler has no way to detect this problem

The problem here is that code like this mixes two paradigms: An object-oriented approach with a choice type (union type). There are two ways to fix this, either by using choice types properly or by using the object-oriented approach properly.

Avoid casts with Choice Types

Using choice types, we could do this:

  Student(redefine name string, id i64) : Person
  {
    study
    {
      ..
    }
  }

  Professor(redefine name string, employeeId i32) : Person
  {
    teach
    {
      ..
    }
  }

  Person : choice<Student, Professor>
  {
  }

  printWithIds(l List<Person>)
  {
    for (p : l)
    {
      match p
      {
        stud Student   => stdout.println(stud.name + " id " + stud.id),
        prof Professor => stdout.println(prof.name + " employee id " + prof.employeeId),
      }
    }
  }

Adding the Assistant as above will cause a compile time unless this new type is added to the alternatives used in Person and all the match statements on Person.

Avoid casts using object-oriented techniques

An object-oriented solution is straigtforward:

  Person ref
  {
    abstract name string;
    abstract idString string;
  }

  Student(redefine name string, id i64) : Person
  {
    redefine idString => "id " + id;

    study
    {
      ..
    }
  }

  Professor(redefine name string, employeeId i32) : Person
  {
    redefine idString => "employee id " + employeeId;

    teach
    {
      ..
    }
  }

  printWithIds(l List<Person>)
  {
    for (p : l)
    {
      stdout.println(p.name + " " + p.idString);
    }
  }

Again, adding an Assitant would cause a compile time error unless the idString feature is implement properly.

Avoid casts by adding features to library code

Sometimes, desired functionality might be missing in an object-oriented design that was fixed in some library module. Say we have the following library

  externalLib
  {
    Person ref
    {
      abstract name string;
    }

    Student(redefine name string, id i64) : Person
    {
      study
      {
        ..
      }
    }

    Professor(redefine name string, employeeId i32) : Person
    {

      teach
      {
        ..
      }
    }
  }

And we want to implement printWithIds in a different module without modifying externalLib.

  myModule
  {
    externLib.Person
    {
      idString => "*** bug, forgot to implement idString for " + myModule.this
    }
    externLib.Student
    {
      idString => "id " + id
    }
    externLib.Professor
    {
      idString => "employeeId "+ employeeId
    }

    printWithIds(l List<externLib.Person>)
    {
      for (p : l)
      {
        stdout.println(p.name + " " + p.idString),
      }
    }
  }

This approach of adding features is a bit similar to implementing traits in Rust. There will have to be similar restrictions, i.e., the added features cannot be visible to the original library module or to other modules using that library.

Open question: Does it make sense to export added features if the module adding them is a library itself?

Reference Type Casts in Fuzion

There should be no cast operator in Fuzion to change the type of a reference. Similarly, there should also be no equivalent to Java's instanceof operation.