Type Conversions

Basic Storm attempts to limit automatic typecasts to avoid situations where information is lost due to an unintended implicit type conversion. This is, however, overly strict in many situations, so Basic Storm allows some level of implicit type conversions that are considered safe.

The remainder of this section describes the different type conversions available, both explicit and implicit ones.

Explicit Type Conversions

There is technically no standard way to perform explicit type conversions in Basic Storm. Instead, Basic Storm relies on the standard library. All primitive types contain member functions for conversion between the relevant member types. For example, one can convert from an integer (core:Int) to an unsigned integer (core:Nat) by calling the .nat member available in the Int type (and most other integer types). Some examples are as follows:

10i.nat;   // Convert Int => Nat
10n.int;   // Convert Nat => Int
10.0.int;  // Convert Float => Int

Most types also have a toS (short for toString) that converts it to a string. Value types may instead have an output operator <<. If that is the case, then a toS function is automatically generated for them in the system.

Other user-defined types typically have constructors for converting between types. As such, for two types might be possible to explicitly call the constructor to convert between the types. For example:

T original;
U copy(original); // Constructor call at creation time
U(original);      // Constructor call as a part of an expression.

Implicit Type Conversions

As previously mentioned, some type conversions are performed implicitly. Basic Storm allows the following conversions implicitly:

Weak Casts

Certain type conversions do not always succeed, but may fail at times. Examples of such casts are casts from a super type to a derived type, and casts from Maybe<T> into T. The cast from a super type to a derived type only succeeds if the dynamic type of the variable matches the expected type, and the cast from Maybe<T> to T only succeeds if the Maybe<T> did not contain null.

To accommodate this type of casts in a safe way, Basic Storm introduces the concept of a weak cast. These casts need to be embedded in a conditional statement so that the compiler can ensure that it is only possible to access the result of the cast if it succeeded.

The remainder of the documentation on weak casts will use if statements to illustrate the behavior. However, it is possible to use weak casts in unless statements and while loops as well.

A weak cast has the following form in general:

if (<variable> = <weak cast>) {
    // <variable> is visible here
} else {
    // <variable> is not visible here
}
// <variable> is not visible here either

The <weak cast> itself can take different forms depending on the cast that is attempted. Currently, the following two forms are implemented. More can be added by extending the rule lang:bs:SWeakCast:

For example, they can be used as follows:

void myFunction(Base? x) {
    if (d = x as Derived) {
        // 'd' is now of type Derived.
    }
}

As a special case, whenever <expression> only refers to a local- or member variable, the <variable> part in the if statement may be omitted. In that case, Basic Storm creates a local variable that shadows the original variable instead. As such, the following two are equivalent:

void myFnA(Base? x) {
    if (x = x as Derived) {
        // 'x' is now of type Dervied.
    }
}

void myFnB(Base? x) {
    if (x as Derived) {
        // 'x' is now of type Dervied.
    }
}

Of course, this works with casts from Maybe<T> into T as well. This makes null-checks look like in C++:

void myFn(Base? x) {
    if (x) {
        // 'x' is now of type Base (i.e. not Maybe<Base> as the parameter is).
    }
}

The <variable> part may also be omitted for other expressions. However, in that case there is no way to access the result of the successful cast.

For completeness, we provide the following examples of using weak casts with unless and while statements:

void f(Int? to) {
    unless (to)
        return -1;

    to + 20;
}

Note that the branch following the unless statement needs to return or throw an exception to ensure type-safety of the to + 20 part of the code. The code above is equivalent to the following:

void foo(Int? to) {
    if (to) {
        to + 20;
    } else {
        -1;
    }
}

Finally, the ability to use weak casts in while loops makes it convenient to use the iterators from WeakSet among others:

void iterate(WeakSet<Int> set) {
    var iter = set.iter();
    while (elem = iter.next()) {
        // Do something
    }
}