Note: Most of this is implemented to some degree.

Typing is strict: You cannot put a round peg in a square hole.

Since all values, expressions and variables have a type, the compiler can (and will) check whether the use of types is consistent. In most cases, an error will be reported wherever a program:

These constraints protect against a program manipulating a value in ways that make no sense or could be harmful, due to acting as if the value has a different meaning than it actually does.

However, sometimes we do want to transform a value of one type to another type. Cone supports three such capabilities:

To distinguish between coercion and conversion, it is first necessary to understand how subtyping works in Cone.


Subtyping captures a simple but powerful idea: if we have two types named Base and Subtype, how do we determine when it is safe to substitute in a value of type Subtype whenever our program logic is looking for a value of type Base?

Here are two essential criteria (there are others):

Many type pairs in Cone comply with these criteria and support a safe subtyping relationship:

An important characteristic of subtyping relationships is that it is trivially safe to transform a Subtype value into its equivalent Base value. However, the journey of a value back from Base to Subtype often requires some sort of pattern-matched guard, as there are likely to be values in Base that do not have a valid mapping from Subtype to Base. A successful pattern match guarantees we will get back a valid Subtype value when the reverse transformation is applied.

Implicit Coercion

We call it coercion when transforming a subtype value into its equivalent base value. Coercion is the one safe exception to the strict typing rule described earlier, given that the type of a value and its receiver are not strictly the same.

Since coercion is always safe, Cone performs it implicitly when copying or moving a subtype value to any container (variable, parameter, return value, field, reference) expecting a base value. This happens during assignments, function calls and returns. For example:

fn doStuff ()
  imm bigint u32 = 101u8        // Coerce a u8 to u32
  mut matrix = &so Mat4[]       // Allocate a matrix
  imm float = mat4det(matrix)?  // Coerce &so uni Mat4 to ?&Mat4

fn mat4det(mat &Mat4) f32 throws DivByZero
  // ...
  return 1.0                     // Coerce f32 to Result<>

Coercion is more than just changing the type of some value. It usually means creating a new value whose encoding is different than its source value.

Coercion can also trigger compiler mechanics: In the case of coercing an owning reference to a borrowed reference, it triggers appropriate borrowing mechanics on the source variable. Similarly, when coercing a non-copy value, move semantics apply.

If an expression requires, it is possible to explicit request coercion. For non-reference types, use the appropriate constructor (as described below for conversion). For references, use the as operator:

imm newref = oldref as &Point  // Explicitly coerce to a borrowed reference

Implicit Coercion of 'self'

Cone offers a very different form of coercion when dealing with method calls (or field access) and the 'self' value. This convenience sugar automatically coerces self from a value to a reference (or the other way).

Cone will automatically transform 'self' to a mutable borrowed reference for operators that expect an lval, such as ++, +=, or a 'set' method.

++a    // equivalent to:  Type::`++`(&mut a)

Automatic de-reference of 'self' may happen when 'self' is a reference, but no method matches with a reference-based self. 'self' is then automatically dereferenced in hopes of finding a matching method that uses a value rather than a reference.

&a + 5  // may coerce to:  a + 5

Explicit Conversion

Type conversion involves transforming a value of one type to the closest possible value in another type. For example, converting the integer 1 to the floating point number 1.0, or serializing it to text: "1". Unlike coercion, conversion may not map perfectly: A valid mapping may not exist or some precision may be lost.

Conversion is typically requested using the new type's constructor or invoking one of its initializers

imm float = f32[1]
imm text = Text(1) // "1"

In some cases, it may be more convenient to request conversion by using some method in the old type:

imm text = 1.text