Constraints
Generic type constraints are a kind of mask that tells which types the generic type will be compatible with. Generic strains will not accept any strain that is not compatible with this mask. Any generic type that does not contain a constraint by default will accept any type.
To define a constraint, use a colon after the generic type and specify the mask. To give more than one mask, define the masks with the | operator. Each mask, separated by |, means a separate acceptability. That is, the constraint does not expect it to match all of them; if any of the masks are valid, it ensures the match.
For example:
fn example[T: int | uint]() {
match type T {
| int:
println("int")
| uint:
println("uint")
}
}In the example above, the T generic will only accept int and uint types.
Constraints can use generics.
For example:
fn sum[S: []T, T: int | uint](s: S): T {
let mut n: T = 0
for _, x in s {
n += x
}
ret n
}In the above example, the T generic accepts a slice using the E generic. The 'E' generic accepts only 'int' and 'uint' types with its constraint.
In some cases, this usage may result in impractical attributions. The software developer cannot overcome the type mismatch error at compile time. The compiler does not guarantee that it will catch such errors.
Masking with Deep Match
By default, a constraint mask is only compatible with the mask type. But sometimes, strict type aliases should be able to pass the constraint. Since strict type aliases are considered totally a different type, using an ordinary mask in the constraint is not proper for that. Because the type will not be matched.
To achieve this, Jule provides the tilde ~ operator. This operator enables deep matching for the mask. So strict type aliases can pass the constraint.
For example:
type Float: f64
fn foo[T: int | f64]() {}
fn main() {
foo[Float]()
}In the example above, the compiler will complain about the constraint of the type T. Because the strict type alias Float will not pass the constraint. For the proper use, type T must be f64.
The tilde ~ operator allows strict type aliases in such cases.
For example:
fn foo[T: int | ~f64]() {}In the example above, the Float type can pass the constraint thanks to the tilde ~ operator. But this is only applied for the f64 type, not for int. The tilde ~ operator enables deep matching for only a single mask, where it is used.
INFO
When the tilde ~ operator is used, the mask type should be a primitive, slice, array, map, chan, or pointer type. Strict type aliases, built-in constraints, structs, and others will not be accepted as masks.
Builtin Constraints
Your compiler has some built-in constraint statements to make things easier. These expressions can be used just like a type, but can also be shaded. If you have a valid type with the same identifier as the built-in constraint you want to use, that type will be considered the constraint.
| Identifier | Constraint |
|---|---|
signed | Signed number types (deep match) |
unsigned | Unsigned number types (deep match) |
integer | Integer number types (deep match) |
float | Floating-point number types (deep match) |
cmplx | Complex number types (deep match) |
numeric | All numeric types (deep match) |
mutable | Mutable types (deep match) |
immutable | Immutable types (deep match) |
comparable | All types which is supports the ==, != operators (deep match) |
ordered | All types which is supports the <, <=, >=, > operators (deep match) |