Cone Language Reference

trust: when you know better than the compiler

On this page

Note: trust is not built yet. It is not yet a keyword, so a program that writes it does not compile, and the operations it is meant to guard (the raw pointer operations, calls to extern functions, and the intrinsics marked "needs trust") compile today without it. What is built is the other half: the casts that need it are refused.

The compiler's gift is rigour: it notices, every time, when code breaks one of its rules. But its rules are simple ones, checked quickly, and it cannot tell when code that breaks one is still entirely safe. Then it is not helping; it is in the way. trust is the way past: written before an expression or a block, it turns certain checks off there, and marks that code as the programmer's responsibility. This page covers what trust allows, the casts that need it, and why it is kept narrow. Here is the compiler stopping at the edge of what it can check:

fn poke(r &P) { let m = r as &mut P; m.x = 99; }

It is refused:

Error 1502: `as` changes the reference's permission from ro to mut, which is not a coercion. Between
references `as` may only be a legal coercion (`uni` to `var`, `let` or `&`; `var` or `let` to `&`);
anything else needs `trust`, which is not built yet.

What trust allows#

Safety comes from the programmer and the compiler working together, each bringing what the other lacks. The compiler will never understand what a program is meant to do as well as its programmer does; the programmer can never match the compiler's tireless consistency. So the compiler checks everything it can, and where its checks fall short, the programmer, who has the final responsibility for safety anyway, says "trust me on this". Preceding an expression or a block with trust allows:

  • Raw pointer dereferences, arithmetic and casts. They bypass every constraint a reference is held to: permissions, lifetimes, regions and bounds checks (Raw pointers).
  • Calls to extern functions. Cone has no way to know whether code written in another language was as careful about safety as Cone is (Calling C).
  • Type reinterpretation that is not also a coercion, where Cone cannot know that the value will be used safely as its new type (below).
  • Access to an untagged overlay's fields, where Cone cannot know which field holds a value (below).
  • The intrinsics whose misuse breaks memory safety, each marked "needs trust" on that page.

Code the compiler cannot verify is then isolated and visible as such, rather than spread through a program, while the programmer stays free to do what they can see is safe. And trust is meant to stay narrow: a remedy for checks that are over-cautious, refusing what is in fact safe because the compiler cannot see far enough to confirm it, not a general way out of the language's rules. A whole function's block can be marked, by writing trust after its signature, as the overlay example below does.

Reinterpretations that need trust#

A reinterpretation with as keeps a value's bits and changes its type. When the new type is one the value could be coerced to, that is safe: someref as ?&i32 makes a reference that may be absent out of one that is not, and compiles today. Any other reinterpretation could let a value misbehave, handled within the rules of a type it does not really belong to, so where Cone cannot be sure of the risk it asks for trust, and the programmer who writes it accepts the responsibility. Reading a reference that may be absent as one that is not, with no check for None in sight, is one:

let someref = trust {nullref as &i32};    // Not built yet: today this cast is refused

So are the reference casts that change a permission other than as a coercion would: a & as &mut, which would write through a reference that promised not to, and a &mut as &uni, which would make the only reference one that other names still reach. Both are refused today, as above, with a message naming both permissions. The bytes-to-text cast, bytes as &str, is allowed today, but it promises that the bytes are text and that nothing changes them while the text is used, which only the programmer can know, so it is for trust too once that is built. Reinterpretation: as has every rule of as, including which casts need nothing at all.

Untagged overlays#

An enum carries a tag, so which variant a value holds is part of the value, and a pattern match is what recovers it. An untagged overlay, several differently-typed fields sharing one piece of storage with nothing saying which is live, gives that knowledge up in exchange for the space. The compiler then cannot verify that the field read is the one last written, because there is nothing for it to check against, and trust is how the programmer tells it not to worry here. This function, the whole of it marked trusted, keeps a small number and a reference in one word, telling them apart by the low two bits:

fn getInt(v Overlay) usize trust {      // Not built yet: no spelling for Overlay, nor trust
  if (v.nbr & 3 == 0) {
    *v.ref;
  } else {
    v.nbr>>2;
  }
}

Cone has no spelling for an overlay. The tagged form is an enum, and it is the one to reach for: what an overlay adds is the obligation to track the live field by hand, which is exactly what trust would be asserting.

You can now tell which operations are the programmer's responsibility, and how Cone's design marks them. Next, two promises a function's own declaration can make: that its body is copied into each caller, and that it touches nothing but its arguments.