The basic idea behind references and pointers is pretty simple. Instead of having a variable holding an object, we want the variable to hold a reference to an object. Using this reference, we want to be able to access or change the object's contents.
Let's illustrate this with a simple example. The & operator obtains a reference to a variable's contents. The * operator accesses the contents referred to by a reference:
imm a = 3 imm ref = &a // ref holds a reference to a's contents imm val = *ref // val is now 3
References (and pointers) offer several benefits:
- It is often more space- and performance-efficient to pass references from function to function than it would be to pass around copies of a much larger object.
- With references, it is possible to create multiple references to the same object, such that all of them see the most current contents of that object. A change that one reference makes to an object is instantly seen by all other references. Objects that are passed around as copies do not have this property, as a change to a copy leaves the original object unchanged.
Safety Challenges with References
Although references can be efficient and versatile, they can also be dangerous. Unless appropriate precautions are taken, use of raw pointers can cause serious logic failures:
- Memory unsafe. A pointer might point to an object that has been deleted and no longer exists. It could also point to an area of memory that has no valid objects (e.g., NULL).
- Type unsafe. A pointer might point to an object whose type is different than it expected (e.g., the program thought it was pointing at an integer, but there is a float there instead.)
- Concurrency unsafe. One pointer might try to access its object while its contents are in the process of being changed by another pointer. Since the contents are in transition, they may not make sense to an outside pointer. Worse would be when two pointers try to change the same object at the same time, making a mess of it in the attempt.
Such failures are not always easy to detect and eliminate. The consequences for such logic failures might be minor, but they could be catastrophic, especially when malicious actors take advantage of undetected pointer problems to gain access or control over other peoples' privileges or information.
References vs. Pointers
One of Cone's key goals is to make it easier to build safe programs. To support this goal, Cone explicitly distinguishes between references and pointers as separate types:
- References are implicitly understood to just be a stand-in for the objects they reference. References are certified memory-, type-, and concurrency-safe by the compiler. Safety is ensured by constraining reference use in specific ways, such as single owner, lifetime and permission constraints as well as forbidding pointer arithmetic. Additionally, runtime mechanisms, such garbage collectors and locks, may be automatically woven into reference use (where requested) to safely improve the flexible use of references.
- Pointers are more powerful than references, being far less constrained by the compiler. Pointer arithmetic is allowed. Pointers have no lifetime or aliasing constraints. However, this added versatility carries a responsibility: the programmer becomes solely responsible for ensuring that all pointer use is safe.
Allocator vs. Borrowed References
References come in two flavors, distinguished by how they are created:
- Allocator References are created and managed by a named allocator. An allocator reference can only ever point to a complete, allocated object. Allocator references preserve their allocator as part of their type. Allocator references are typically free from lifetime constraints.
- Borrowed References are borrowed from a variable or some other reference. Borrowed references are more flexible, as they have no idea what allocator created the object and are able to point to substructures within compound objects. This flexibility makes them ideal for passing information to general-purpose functions or methods. However, the lifetime of a borrowed reference is constrained; it cannot outlive the variable or reference it borrowed from.
Subsequent chapters provide detailed information about each of these distinct types: