We want programs to be well-behaved, doing what we intend without misbehaving in the process. Unfortunately, mistakes are both inevitable and costly. An obvious cost is the productivity loss due to correcting found mistakes. The more significant cost may result from security and opportunity risks due to latent bugs.
Compile-time Safety
To help mitigate these costs, the Cone compiler can detect and prevent these safety violations:
- Null pointer exceptions. References are not nullable by default. When a reference is nullable, it may only be used after runtime logic first ensures it is not null
- Uninitialized values. It is not legal to access a variable's, field's, or reference's value until it has at least been given some valid, initial value.
- Out-of-bounds access. Array indexing is only permitted when the index lies wholly wihin the start and end bounds of the array.
- Dangling pointer. All references to an object cease to exist before the object is freed. Thus, a reference cannot be used-after-free nor can an object be freed twice.
- Data races. One reference may not access an object while another reference is non-atomically mutating it.
- Type violation. Every variable, field and reference can only hold values of their declared type. Further, if a value can unwrap to one of several variant types, runtime logic must first determine which variant it holds before unwrapping it.
Safety is the Programmer's Responsibility
At the end of the day, the compiler can only act as a valuable assistant, skilled at pointing out inconsistencies and obviously-violated constraints. It is up to the programmer to write code that protects against:
- Memory waste.
- Stack and memory overflow.
- Deadlocks and other race conditions.
- Non-terminating loops.
- Broken invariants.
- Unanticipated mutation.
- Fragile dependencies.
- Namespace pollution.
- Arithmetic errors.
- and any other kind of bad logic...
To help reduce the likelihood of experiencing the above problems, the experienced programmer can leverage helpful language features, such as: exception handling, namespace and mutation isolation, bounded iteration, private fields on types (to help protect invariants), polymorphism (when inheritance is unnecessary), finalizers, and move semantics.
Versatility vs. Safety
Although safety matters a lot, we don't want compiler-enforced safety features to negatively impact the productivity of the professional programmer. As much as possible, there should be a straightforward way for the programmer to write code that their judgement, experience and tests demonstrate is safe. Over the next few pages, let's explore several fine-grained controls the programmer can use to customize the language's built-in safety constraints.