Cone Language Reference

Safety and the boundary with C

On this page

Mistakes are inevitable, and the costly ones are those that hide: a reference to memory already freed, two threads changing one value, bytes read as the wrong type. Cone's compiler catches whole classes of them before a program runs, or stops the program the moment one shows. This part says what it promises, where its promises stop (at C, at raw pointers, at trust), and what a program does when something goes wrong anyway. Afterwards you know which guarantees you are relying on, and how to step outside them on purpose. Here is a guarantee at work:

mod guard;

import stdio use *;

fn main() {
  let shelves = [12, 0, 7];
  var total = 0;
  var n = 0usize;
  while n <= 3usize {           // one pass too many: the shelves are 0, 1 and 2
    total += shelves[n];
    n += 1usize;
  }
  print <- "loaves: ", total, "\n";
}

Instead of adding whatever lies past the end of the array, it stops, and says why:

panic at guard.cone:10: index 3 is out of bounds for a count of 3

What the compiler guarantees#

Cone's compiler detects and prevents these violations of safety, each by a mechanism this manual describes where it is used:

  • Null pointer exceptions. A reference is never null. A value that may be absent is an Option, which can be used only after a check finds something in it; only a raw pointer may be null.
  • Uninitialized values. A variable's, field's or reference's value cannot be read until it has been given a valid initial value: a local declared now and assigned later (Variables, types and expressions), or a global its module's initializer assigns (Global state and initialisation).
  • Out-of-bounds access. An index into an array, list or slice, and a range taken from one, must lie within it. The check runs as the program does, and a failure panics, as above. Only a raw pointer's index is unchecked (Raw pointers).
  • Dangling pointers. Every reference to a value is gone before the value is freed, so a reference cannot be used after the value is freed, nor the value freed twice (Borrowed references, Lifetimes, Regions).
  • Data races. No reference may reach a value while another is changing it without atomic operations (Permissions, Lock permissions, What may cross a thread).
  • Type violations. Every variable, field and reference holds only values of its declared type (Type safety), and a value that may be one of several variants must be matched to find out which before its contents are used (Enums).

These are the language's promises. Not every check behind them is complete yet, and trust, which is to mark the operations the compiler cannot check, is not built, so those operations compile without it for now.

What stays the programmer's job#

A compiler can point out inconsistencies and constraints plainly broken. It cannot know what a program is meant to do, so these remain the programmer's to prevent:

  • memory waste, and running out of stack or memory;
  • deadlocks, and race conditions other than data races;
  • loops that never end;
  • broken invariants, and mutation nobody expected;
  • fragile dependencies, and names that crowd a namespace;
  • arithmetic errors: integer overflow wraps silently, and division by zero is not checked;
  • and every other kind of wrong logic.

The language still helps: errors reported with Result (exceptions are planned), isolation of names and of mutation, iteration that is bounded by what it walks, private fields that protect a type's invariants, traits where inheritance is not needed, finalizers, and moves.

A teammate, not a guard#

The compiler will never understand what a program is for as well as the programmer does. What it brings is rigour no person can match: it notices every time code breaks a rule it has been given. Safety comes from the two working together, and the programmer keeps the final responsibility.

Most of Cone's checks are made as it compiles and cost nothing at run time (bounds checks are the exception); they are paid for in complexity instead. References carry permissions and lifetimes, and a program spells out what a pointer never would. That is the real price of safety, the attention it asks of the programmer, and it is better named than hidden. So where a rule refuses code that the programmer's judgement, experience and tests show is safe, there should be a straightforward way to write it anyway: in the open, marked as the programmer's responsibility. That is what this part's later pages are about. Likewise, a few data structures with several owners are not worth proving safe in the language at all; they are better built with raw pointers behind a small, safe interface.

In this part#

The first of these is the one you will use every day: getting a value from one type into another without losing what it means.