The primary responsibility for the correct operation of a program lies with the programmer. However, a language's compiler and standard library can go a long way to making this job easier. The compiler will never understand the program's intended behavior as well as the programmer, but the compiler possesses a rapid, disciplined rigor that fallible humans can never rival. Working together as a team is the quickest path to minimizing risk.
System programming languages have traditionally had a poor reputation for ensuring safety (allowing data buffer overruns, null pointer dereferencing, use after free, data races, etc.). Such vulnerabilities don't just cause programs to fail, they can also open systems up successful attacks by malicious actors.
Rust demonstrates that smarter, safer compilers can be built, able to identify safety exposures without sacrificing runtime performance and flexibility. Cone follows Rust's lead here.
Cone imposes several constraints to prevent bad memory access:
- Use-after-free. Each of Cone's regions uses a different technique for calculating when an object's lifetime expires, such as RAII-based escape analysis, reference counting and tracing. Once there are no live references to it, an object is cleaned up (using the finalizer) and then freed. Since no usable refeerences point to the object, there is no way for it to be used after free.
- Out-of-bounds. All collection types in the standard library perform boundary checks, based on the known size of the collection, before attempting to access a specific element in a collection.
- Pointer arithmetic. Cone references do not support arithmetic operations, such as incrementing. Thus, it is not possible to calculate a reference that points to an invalid location.
Cone's static and runtime permissions ensure that two threads cannot modify the same value at the same time. The static permissions accomplish this by preventing two threads from having shared mutable access to the same value. The runtime permissions enforce that all access to shared mutable data will be serialized through the use of synchronization mechanisms.
Cone expects every value to be typed. In general, Cone will fail to compile any program that attempts to handle a value of one type according to the rules of another type. The only exceptions:
- Implicit number conversions
- Using pattern matching to extract a value of a specific type from a sum typed value
The safety constraints listed above help ensure programs cannot exhibit unsafe behavior. However, these constraints are sometimes overzealous, preventing behavior that may actually be safe, but which the compiler does not have the insight to confirm. For situations like these, Cone provides a useful exit hatch: "trust" blocks. Within trust blocks, Cone's constraints are relaxed, trusting that the programmer takes responsibility for ensuring the program's safety. Within trust blocks, a programmer may do pointer arithmetic, call external functions written in unsafe languages, cast references to other types, etc.