Before talking about Cone's distinctive type features, it would be prudent to offer a worldwind tour that shows how much of Cone's type system is deliberately familiar. For example, Cone's concrete types are largely derived from C and ML dialects:

Generic versions of any type can be defined, using type-constrained parameters.

Inspired by OOP languages, all named (nominal) types support methods. Many operators are implicitly implemented as methods. Methods may be overloaded.

Like Rust, Cone supports several useful (and infectious) type constraints:

Type safety is enforced strictly. With the notable exception of subtype substitution, values must stay in their type lanes. Even better, nullability is not the default: one must explicitly type a value as nullable using Option[T] or '?'. Similarly, one cannot throw an exception up the call stack. Instead, failures are returned using Result type values.

To reduce the burden on the programmer, the Cone compiler supports bi-directional type inference. This minimizes how often type information needs to be annotated in the program.

In summary, Cone's rich static types improve long-term development productivity, as they help you catch more errors sooner. These static types also make it possible to build faster programs, because you can design data structures that can be optimized for what runs quickly on modern CPUs.

Cone's Distinctive Type System Features

Even though Cone's type system is mostly derivative (although delightfully re-mixed), several type features are either new or significantly enriched in Cone. These improvements improve your productivity, by making it easier to express common and useful design patterns in your code, particularly adding dynamic type-like flexibility, without losing the performance and safety guarantees of static types:

Let's walk through each of these in turn.

Traits

Many languages support some form of existential polymorphism, calling the feature traits, interfaces, abstract classes, typeclasses, concepts, protocols, etc. In general terms, this feature is used to define an abstract pattern of function or method signatures that multiple types can conform to, for substitution purposes.

Cone calls these traits. Cone's traits carry some distinctive improvements:

Variant Types

Often we want type flexibility, such that some value can (at runtime) be one of several possible types. Most languages offer only one way to accomplish this, either with sum types or using inheritance. Cone supports three different flavors of variant types, each with different advantages and constraints:

All flavors of variant types support methods, shared fields, virtual dispatch and pattern matching.

In most cases, union types are the preferable choice for speed and support for the largest variety of design patterns, so long as its okay that the number of variants can all be defined in one module and all be padded to the same size. However, when these restrictions are not acceptable, it is nice to be able to make use of trait-based variants.

Robust Subtyping

Relevant to our conversation about traits and variant types, is how extensively and safely subtyping is supported by Cone. Subtyping reflects the idea that a value of some type may be safely substituted into the corresponding value of its supertype. Variant types are subtypes of their union or base trait. Region-managed references are subtypes of borrowed reference. Some permissions are subtypes of other permissions. And so on.

Subtyping is an important component for supporting polymorphic, flexible types, in that it ensures that safety is not compromised. Cone's subtyping rules are sophisticated enough to correctly handle variance requirements in the presence of references, mutation and function signatures.

Subtype substitution can happen at compile-time (via parametric types on generics) or at runtime (via automatic runtime coercion). The rules for runtime substitution are less flexible due to to memory constraints.

Runtime subtyping is particularly valuable for:

Delegated Inheritance

In some circles, inheritance has gotten a bad name for being problematic and unnecessary. Who doesn't flinch at large, complex programs that become hard to maintain due to deep inheritance trees and fragile base classes?

When you have a powerful independent mechanism for polymorphism in traits, the need for implementation inheritance is definitely reduced. That said, there still are times when it is handy for one type to be able to reuse methods implemented by another type that is part of its composition.

Cone offers a distinctive form of inheritance called "delegated inheritance" which satisfies this need. Consider this example:

struct Engine
  fuel   f32
  torque f32

  fn thrust(amt f32) { ... }
  fn power(on Bool) { ... }

struct Car:
  engine Engine use fuel, thrust
  body   SportyLook
  wheels RimWheels

As part of its composition, Car has an Engine. The engine field specifies "use" to request delegated inheritance for the Engine's fuel field and thrust method. This means that car.thrust(2.3) is legal, and will be forwarded automatically to Engine's thrust method like this: car.engine.thrust(2.3). It is nice to be able to do this without requiring all the ceremony of building various delegation methods on Car, as recommended by the "Favor Composition Over Inheritance" gang of four.

By using delegated inheritance, you can exploit the expressiveness of method and field reuse between types, while avoiding the entangling complexity of traditional inheritance.

Method Extensions

Sometimes, we import types from a package, but wish it supported more capabilities (methods) than it does. Cone's "extend" feature makes it possible for one module to extend the methods supported by an imported type. This would make it possible to import an floating-point number type that only supports arithmetic operations, and then extend it to also support logarithmic or trigonometric operations. In effect, Cone's "extend" feature provides a workable solution to the so-called expression problem.