> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# The type system

> The types of RSL, the facts that a type carries about its values, and how the check uses those facts.

RSL is statically typed.
Each input and each calculation states its type, and the check holds every expression to it.
A type also carries facts about its values, such as a range, and the check uses those facts to show each operation safe.

## The types

| Kind | Types | A value is |
| - | - | - |
| [Numbers](https://docs.canary.effectiveai.app/rsl/reference/language.md#numbers-and-precision) | `Num`, `Premium`, `Rate`, `Factor`, `Pct` | An exact rational number |
| [Scalars](https://docs.canary.effectiveai.app/rsl/reference/language.md#scalars) | `Bool`, `Date` | `true` or `false`; a calendar day |
| [Enumerations](https://docs.canary.effectiveai.app/rsl/reference/language.md#enumerations) | A named set, written in the file or read from a CSV file | One member of the set |
| [Bands and value lists](https://docs.canary.effectiveai.app/rsl/reference/language.md#bands-and-value-lists) | `IntBands`, `DecimalBands`, `IntValues`, `DecimalValues` | A key of a rate table |
| [Codes](https://docs.canary.effectiveai.app/rsl/reference/language.md#codes) | `Code(pattern: "…")` | Text that matches a pattern, such as a postal code |
| [Options](https://docs.canary.effectiveai.app/rsl/reference/language.md#option) | `Option(T)` | Present, as `Some(value)`, or absent, as `None` |
| [Records](https://docs.canary.effectiveai.app/rsl/reference/language.md#records) | A named record | A set of inputs, rules and calculations |
| [Lists](https://docs.canary.effectiveai.app/rsl/reference/language.md#list-ref-and-key) | `List(Record)`, `Ref(Record)`, `Key(Table)` | Records in a list; one item of a list; one filed key of a table |
| [Unions](https://docs.canary.effectiveai.app/rsl/reference/language.md#unions) | `A \| B` | One record out of several, with a tag |
| [Maps](https://docs.canary.effectiveai.app/rsl/reference/language.md#maps) | `Rate[Coverage]` | One value for each member of an enumeration |

[The language reference](https://docs.canary.effectiveai.app/rsl/reference/language.md#types) gives the form and the rules of each one.

## A value keeps its type

RSL has zero implicit conversions. This calculation adds a `Bool` to a number:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
x: Num = self.a + self.c,
```

> [accordion] convert/rater.rsl
>
> ```rsl convert/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Types",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     k [cite: manual:p1] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             a: Num {precision: int} where self in [1, 10],
>             c: Bool
>         },
>         step rating {
>             x: Num = self.a + self.c,
>             premium: Premium [cite: manual:p1] = round(self.x * k, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check convert/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL convert/rater.rsl (strict)
error: E-TYPE convert/rater.rsl:27:31: arithmetic operand expects Num, got Bool
```

An expression states the conversion it wants, such as `if self.c then 1 else 0`.

## A type carries facts

A numeric input states its precision and its range:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
a: Num {precision: int} where self in [1, 10],
```

The `where` clause is a **refinement**: it narrows the type to the values that pass it.
A request with a value outside the range is refused before any calculation runs.

From the facts of the inputs, the check works out the facts of each calculation.
This one declares a range of 0 to 5 for the sum of two inputs:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
x: Num {precision: int} where self in [0, 5] = self.a + self.b,
```

> [accordion] range/rater.rsl
>
> ```rsl range/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Types",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     k [cite: manual:p1] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             a: Num {precision: int} where self in [1, 10],
>             b: Num {precision: int} where self in [1, 10]
>         },
>         step rating {
>             x: Num {precision: int} where self in [0, 5] = self.a + self.b,
>             premium: Premium [cite: manual:p1] = round(self.x * k, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check range/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL range/rater.rsl (strict)
error: E-COMPUTED-RANGE range/rater.rsl:27:60: `x` declares range 0..5, but its body has range 2..20; narrow the inputs it reads, clamp the body with min/max, or delete the declared range
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

The check tracks these facts about a value:

| Fact | Example | Used to show |
| - | - | - |
| A range | `1..10` | A divisor is nonzero; a key is inside a table |
| A decimal precision | Whole numbers; cents | A value sits on the grid that its type declares |
| A finite set | The members of an enumeration | A `match` has an arm for every value |
| Nonzero | After `x != 0` | A division is safe |
| Non-empty | After `len(list) > 0` | `min`, `max` and `avg` have a value |
| Present | Inside `Some(v) =>` | An optional value can be read |

## A condition narrows a type

Inside a branch, the check knows what the condition says.
Here the divisor has the range -9 to 9, and the guard removes 0 from it:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
x: Num = if self.a != self.b then self.a / (self.a - self.b) else 0,
```

> [accordion] narrow/rater.rsl
>
> ```rsl narrow/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Types",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     k [cite: manual:p1] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             a: Num {precision: int} where self in [1, 10],
>             b: Num {precision: int} where self in [1, 10]
>         },
>         step rating {
>             x: Num = if self.a != self.b then self.a / (self.a - self.b) else 0,
>             premium: Premium [cite: manual:p1] = round(self.x * k, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check narrow/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS narrow/rater.rsl (strict)
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

A `match` narrows in the same way: the arm `Some(v) =>` reads a present value, and each arm of an enumeration reads one member.

## Absence is a type

RSL is free of null. A value that can be absent has the type `Option(T)`, and a calculation reads it with a `match` on both cases.
A read that skips a case gets [`E-OPTION-MATCH`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/calculations#e-option-match).

A [follow-up input](https://docs.canary.effectiveai.app/rsl/reference/language.md#follow-up-inputs) is asked only under a condition.
A calculation reads it under that condition, so an unasked question stays distinct from an answer of `false`.

## Lists and maps

A plain `List(Record)` keeps the order of the request.
A list declared `keyed by` is a map from key to record: its order is free to change, and a selection breaks a tie by the key.
[Lists and keys](https://docs.canary.effectiveai.app/rsl/reference/language.md#lists-and-keys) gives the rules.

## What the type system leaves out

* **Generic types and user-defined functions.** The functions are a fixed list, in [the function reference](https://docs.canary.effectiveai.app/rsl/reference/functions.md).
* **Arithmetic on units.** `Premium`, `Rate`, `Factor` and `Pct` state the role of a number, and each is an exact rational. This sum of a premium and a factor compiles:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
x: Num = self.base + self.f,
```

> [accordion] units/rater.rsl
>
> ```rsl units/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Types",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     k [cite: manual:p1] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             a: Num {precision: int} where self in [1, 10],
>             b: Num {precision: int} where self in [1, 10]
>         },
>         step rating {
>             f: Factor = self.a / 10,
>             base: Premium [cite: manual:p1] = round(self.b * 100, 2, half_up),
>             x: Num = self.base + self.f,
>             premium: Premium [cite: manual:p1] = round(self.x * k, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check units/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS units/rater.rsl (strict)
cost premium: 4 (each list of size 1: 4, 3: 4, 10: 4)
```

The check does flag one case: a `Premium` whose expression holds only factors.

## Related

* [Language guarantees](https://docs.canary.effectiveai.app/rsl/guarantees.md) states what a passing check means.
* [Exact numbers](https://docs.canary.effectiveai.app/rsl/explanation/exact-numbers.md) explains precision and rounding.
* [Missing answers](https://docs.canary.effectiveai.app/rsl/explanation/missing-answers.md) explains optional values and follow-up inputs in a request.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.