-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Rigidity/type-system
Add Type System docs
- Loading branch information
Showing
6 changed files
with
319 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
--- | ||
slug: /pair-types | ||
--- | ||
|
||
# Pair Types | ||
|
||
A pair has two values, a `first` and `rest`. You can create a pair like this: | ||
|
||
```rue | ||
let pair = (42, 34); | ||
``` | ||
|
||
## Lists | ||
|
||
Pairs can be nested arbitrarily: | ||
|
||
```rue | ||
let chain = (100, (200, (300, nil))); | ||
``` | ||
|
||
In fact, the above structure is identical to a nil-terminated list with those items. | ||
|
||
```rue | ||
let chain = [100, 200, 300]; | ||
``` | ||
|
||
These both have the following type: | ||
|
||
```rue | ||
(Int, (Int, (Int, nil))) | ||
``` | ||
|
||
Which can be assigned to the recursive type `List<Int>`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
--- | ||
slug: /type-checking | ||
--- | ||
|
||
# Type Checking | ||
|
||
Types can be assigned or cast to different types so long as they have the same structure. However, sometimes you need to distinguish between and narrow types at runtime. This is when type checking comes in. | ||
|
||
Type checks have the following form: | ||
|
||
```rue | ||
value is Type | ||
``` | ||
|
||
They emit the code required to check the type at runtime, and return a `Bool` value. | ||
|
||
## Structure | ||
|
||
If you have a value of type `Any`, you can determine whether it's an atom or a pair. | ||
|
||
For example: | ||
|
||
```rue | ||
fun count_atoms(value: Any) -> Int { | ||
if value is Bytes { | ||
1 | ||
} else { | ||
let first = count_atoms(value.first); | ||
let rest = count_atoms(value.rest); | ||
first + rest | ||
} | ||
} | ||
``` | ||
|
||
## Length | ||
|
||
You can check against a type with a fixed length: | ||
|
||
```rue | ||
fun calculate_coin_id( | ||
parent_coin_info: Bytes, | ||
puzzle_hash: Bytes, | ||
amount: Int, | ||
) -> Bytes32 { | ||
assert parent_coin_info is Bytes32; | ||
assert puzzle_hash is Bytes32; | ||
sha256(parent_coin_info + puzzle_hash + amount as Bytes) | ||
} | ||
``` | ||
|
||
## Values | ||
|
||
You can even check against a specific integer value: | ||
|
||
```rue | ||
let num: Any = 42; | ||
assert num is 42; | ||
``` | ||
|
||
Although if you have an `Int` already, you may as well just do a simple equality check: | ||
|
||
```rue | ||
let num = 42; | ||
assert num == 42; | ||
``` | ||
|
||
## Complex Checks | ||
|
||
You can check against more complicated nested types as well: | ||
|
||
```rue | ||
struct Point { | ||
x: Int, | ||
y: Int, | ||
} | ||
let value: Any = 42; | ||
assert !(value is Point); | ||
``` | ||
|
||
## Union Narrowing | ||
|
||
If you have a union of one or more values, you can check if it's one of the items in the union. This will narrow the type if it's not. | ||
|
||
```rue | ||
let value: Bytes32 | nil = nil; | ||
if !(value is nil) { | ||
// value must be Bytes32 | ||
} | ||
``` | ||
|
||
## Recursive Checks | ||
|
||
You can check against recursive types only if it would disambiguate a union: | ||
|
||
```rue | ||
let list: List<Int> = [1, 2, 3]; | ||
assert value is (Int, List<Int>); | ||
``` | ||
|
||
However, if you were to try to do this, it would fail since it's a recursive type: | ||
|
||
```rue | ||
let value: Any = 42; | ||
assert value is List<Int>; // Type error | ||
``` | ||
|
||
To achieve this, you can write your own recursive function to check instead: | ||
|
||
```rue | ||
fun is_int_list(value: Any) -> Bool { | ||
match value { | ||
(Int, Any) => is_int_list(value.rest), | ||
nil => true, | ||
_ => false, | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
--- | ||
slug: /type-system | ||
--- | ||
|
||
# Type System | ||
|
||
## Builtin Types | ||
|
||
Rue has a structural type system, which is similar to [duck typing](https://en.wikipedia.org/wiki/Duck_typing). One key difference is that types with different semantics must be cast, even if they have the same structure. | ||
|
||
### Any | ||
|
||
Makes no assumption about the structure of the value. Anything can be assigned to `Any`. | ||
|
||
```rue | ||
let value: Any = (14, 32); | ||
``` | ||
|
||
### List | ||
|
||
Represents a recursive structure of nested pairs. | ||
|
||
```rue | ||
let value: List<Int> = [1, 2, 3]; | ||
``` | ||
|
||
### Bytes | ||
|
||
Any atomic CLVM value can be represented as `Bytes`. However, only strings and hex literals are treated as `Bytes` by default. | ||
|
||
```rue | ||
let hex: Bytes = 0xFACE; | ||
let string: Bytes = "Hello"; | ||
``` | ||
|
||
When you add byte values together, they will be concatenated. | ||
|
||
### Bytes32 | ||
|
||
When an atomic value is exactly 32 bytes in length, it can be represented as `Bytes32`. This enhances type safety of things such as sha256 hashes. | ||
|
||
```rue | ||
let hash: Bytes32 = 0x38b1cec180a0bc0f5ec91097cec51971df126e3b18af54ddba4a3e4a36f9c285; | ||
``` | ||
|
||
### PublicKey | ||
|
||
When an atomic value is exactly 48 bytes in length, it can be represented as `PublicKey`. | ||
|
||
More specifically, `PublicKey` is a [BLS12-381](https://en.wikipedia.org/wiki/BLS_digital_signature#BLS12-381) G1Element. | ||
|
||
```rue | ||
let pk: PublicKey = 0xb3596acaa39f19956f77b84cef87a684ea0fec711e6ec9e55df3cffd4a6e05d3e2da842433dccb6042ee35c14d892206; | ||
``` | ||
|
||
When you add public keys together, it will call the CLVM `g1_add` operator (formerly known as `point_add`). | ||
|
||
### Int | ||
|
||
Any atomic CLVM value can be represents as `Int`. | ||
|
||
```rue | ||
let num: Int = 42; | ||
``` | ||
|
||
You can perform standard arithmetic on integers, as well as bitwise math: | ||
|
||
```rue | ||
let a: Int = ((42 * 34) / 8 + 9 - -10) % 16; | ||
let b: Int = ((100 >> 3) << 2) & 420 | ~6; | ||
``` | ||
|
||
### Bool | ||
|
||
A boolean is either `true` or `false`. | ||
|
||
```rue | ||
let flag: Bool = true; | ||
``` | ||
|
||
You can use logical operators on booleans: | ||
|
||
```rue | ||
let value: Bool = (true && false) || true; | ||
``` | ||
|
||
### nil | ||
|
||
The simplest type of them all, `nil` only has one value: | ||
|
||
```rue | ||
let value: nil = nil; | ||
``` | ||
|
||
## Inference | ||
|
||
In many cases, the type of variables can be inferred based on their value. | ||
|
||
For example: | ||
|
||
```rue | ||
let value = 42; | ||
``` | ||
|
||
Here, `value` is known to be `Int` even though it wasn't specified. It's generally cleaner to omit the type in places where it's obvious. | ||
|
||
## Casting | ||
|
||
### Structural Cast | ||
|
||
You can cast the type of a value with the `as` keyword. This can only be done as long as both types are structurally related. | ||
|
||
For example, casting an `Int` to `Bytes` is fine since both of them are atomic values: | ||
|
||
```rue | ||
let value = 42; | ||
let bytes = 42 as Bytes; | ||
``` | ||
|
||
You can cast more complicated types such as pairs: | ||
|
||
```rue | ||
let pair = ("Hello, ", "world!"); | ||
let casted = pair as (Int, Int); | ||
``` | ||
|
||
However, this is **not** allowed, since the structure would differ (and this could cause runtime errors): | ||
|
||
```rue | ||
let pair = (42, 34); | ||
let num = pair as Int; // Type error | ||
``` | ||
|
||
### Reinterpret Cast | ||
|
||
Sometimes, you need to change the type of a variable without the type system getting in the way. | ||
|
||
:::warning | ||
This is unsafe and could result in hard to debug issues at runtime if you are not careful. It should be scrutinized when auditing Rue code. | ||
::: | ||
|
||
There is a built in function called `cast` which will do this: | ||
|
||
```rue | ||
let pair = (42, 34); | ||
let num = cast::<Int>(pair); // Ok | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters