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:
- Stores a value of one type into a variable or collection declared for a different type.
- Passing an argument of one type to a function that expects a value of another type in that position.
- Returns a value of one type when the function's signature declares a different type for the return value.
- Creates a reference (or pointer) to an object whose type does not match the reference's type declaration.
These constraints protect against the 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 need to transform a value of one type to another type. Cone supports three such capabilities:
- Coercion, which maps a value to its exact equivalent in a supertype.
- Conversion, which chooses the closest "equivalent" value in another type. This is not a perfect mapping: A valid mapping may not exist or some precision may be lost.
- Reinterpretation, which keeps the original value but interprets it as if it has a different type.
Subtyping and Implicit Type Coercion
In many cases, one type can be a subtype of another type. A subtype specializes its more general-purpose supertype, adding additional constraints. Many forms of subtyping are supported:
- A struct is a subtype of any trait it implements or interface it conforms to
- A type is a subtype of any variant type that includes that type as a variation
- A smaller number type is a subtype of a larger version of the same type (e.g., u8 is a subtype of u32)
- Some permissions are subtypes of others (e.g., 'const' is a subtype of 'imm')
- Borrowed references are subtype of an equivalently typed allocated reference (e.g., &Foo is a subtype of &own Foo)
Coercion maps a value to its exact equivalent in a supertype. Cone will automatically and implicitly coerce a value of some subtype to its supertype when a supertype value is expected. Commonly, this happens with assignments and function calls:
// Implicitly coerce a u8 to u32 imm u8value = 101u8 imm bigint u32 = u8value // Implicitly coerce &own uni Mat4 to &const Mat4 mut matrix = &own Mat4 imm det = mat4det(matrix) fn mat4det(mat &Mat4) f32 // ...
Coercion can be requested explicitly by using the 'into' operator:
imm bigint = u8value into u32
Type conversion involves transforming a value of one type to its "equivalent" 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.
Converting one number type to another uses the "literal" format or the 'into' operator:
imm float = f32 imm float = 1 into f32
Beyond number types, conversion is typically requested using the new type's initializer or a method in the old type:
imm text = Text(1) // "1" imm text = 1.text
Unlike coercion and conversion, reinterpretation does not create a new value. It interprets the existing encoded value as if it has a different type. The new type must have the same size as the current type of the value.
Reinterpretation uses the as infix operator. Unlike other infix operators, as expects a type (rather than a value or expression) to the right of the operator.
Reinterpretation is mostly used on references, such as when turning a reference into a pointer.
imm ptr = &x as *Vector3
Reinterpretation can be also be used on number types, such as when we want to access a floating point number's bits:
imm highword = u32[(pi as u64) >> 32]
Reinterpretation can be dangerous, as a value could behave badly when treated and manipulated within the constraints of a different type. For example, a reference reinterpreted as a pointer loses permission constraints, making it possible for a pointer to be used to alter an immutable object. It is the responsibility of the programmer to ensure the reinterpreted value is handled in a safe and useful manner.