Part 2: States

In our new programming language, classes can define states. For example, a LightSwitch is always either Off or On (never both):


class LightSwitch {
  state On;
  state Off;
}

Methods, including constructors, can change the state of this with the -> operator:


  LightSwitch() { // constructor
    ->Off; // Transition to Off state.
  } 

Methods can specify what states the object must be in before they can be invoked and what states the object will be in after they exit by annotating the this parameter. Constructors can specify what state they end in.


  LightSwitch@Off() { // constructor always ends with the object in Off state
    ->Off;
  } 

  void turnOn(LightSwitch@Off >> On this) // turnOn() can only be called on objects that are in Off state.
  {
    ->On;
  }
  
  void turnOff(LightSwitch@On >> Off this)
  {
    ->Off;
  }

Each object can have one reference that statically specifies what state the object is in. For example, LightSwitch@On is the type of a variable that refers to a switch that is in On state.

Note that this is an extension of ownership: like ownership, one reference is special. The compiler keeps track of the possible states an object can be in and makes sure that the specifications are observed. For example:

void foo() {
    LightSwitch@Off s = new LightSwitch();
    s.turnOn(); 
}

The compiler checks method invocations to make sure they are safe:

void foo() {
    LightSwitch@Off s = new LightSwitch();
    s.turnOff(); // COMPILE ERROR: turnOff() requires that s is On, but here s is Off
}

Unowned references

When there may be an owner of an object, other references cannot be used to modify the state. These other references are annotated Unowned. For example:

void foo(LightSwitch@Unowned s) {
    s.turnOff(); // COMPILE ERROR: can't change state of s through an unowned reference
}

Shared references

If there is no owner of an object, then all references to the object are annotated Shared. These references can be used to change the state of the referenced object, but invoking methods that can only be called in some states requires a runtime check. For example:

void test1(LightSwitch@Shared s) {
    s.turnOn(); // COMPILE ERROR: turnOn requires that s be Off, but s is Shared.
}

In the above situation, the programmer might need to check the state dynamically with if...is.

Testing states with is


void test2(LightSwitch@Shared s) {
    if (s is On) { // runtime check to see whether the object referenced by s is in state On
        s.turnOff(); // OK due to runtime check
    }

}

Within the scope of the if...is block, the compiler requires that if there is an owner of the object referenced by s, then the owner's state specification is never violated. If it is, then the program is terminated; it is up to the programmer to make sure the body of the if is block does not change the state inappropriately.

Implicit casts

When a Shared reference is needed, an Owned suffices as long as the reference is NOT to an asset. For example, an Owned reference can be passed as an argument to a method that expects a Shared reference to a non-resource object. However, the caller is left with a Shared reference.

When an Unowned reference is needed, any reference suffices, and the caller is left with their original kind of reference.