Kiểu dữ liệu
Mọi biến đều phải mang kiểu khai báo. Không suy luận kiểu ẩn, không đổi kiểu
ẩn, không che bóng tên, và điều kiện phải là bool thật. Một người kiểm toán
đọc từng dòng và biết chính xác điều gì xảy ra — đó không phải phong cách lập
trình, đó là một yêu cầu kiểm toán.
Bảng kiểu
Phần tiêu đề “Bảng kiểu”| Kiểu | Ý nghĩa | Biểu diễn C |
|---|---|---|
int |
Số nguyên có dấu 64-bit, kiểm tra tràn | int64_t |
float |
IEEE 754 binary64 | double |
bool |
true / false |
bool |
str |
Chuỗi byte, độ dài tường minh | hl_str* |
list[T] |
Mảng động | hl_list* |
map[str, T] |
Bảng băm, khoá str |
hl_map* |
tainted[T] |
Bọc taint — thuộc tính lúc biên dịch | biểu diễn như T |
Cap[E] |
Capability token cho effect E |
— |
void |
Chỉ dùng làm kiểu trả về | — |
Name, Name[T…] |
Struct hoặc enum do người dùng định nghĩa | Name* |
Không có implicit cast
Phần tiêu đề “Không có implicit cast”int → float cần x.to_float(); chiều ngược lại cần x.to_int().
| Từ | Đến | Cách |
|---|---|---|
int |
float |
x.to_float() — chính xác |
float |
int |
x.to_int() — cắt cụt về 0 |
| bất kỳ | str |
x.to_str() |
str |
int |
int_parse(s) trả Result[int, str] |
list[T] |
list[U] |
nhóm list_convert_* của std.list |
int → float là chính xác vì int là 64-bit còn float có mantissa 64-bit.
float → int thì cắt cụt, và ngôn ngữ không giấu việc đó.
Struct và enum
Phần tiêu đề “Struct và enum”struct Point { x: int, y: int}
enum Shape { Circle(float), Rect(int, int)}
fn area(s: Shape) -> float { return match s { Shape.Circle(r) => 3.14159 * r * r, Shape.Rect(w, h) => (w * h).to_float() }}match là một biểu thức và phải phủ hết các biến thể. Đây là nơi “không
null” được thực thi ở mức kiểu: không có null để kiểm tra, nên Option[T]
và Result[T, E] là hai cách duy nhất để biểu diễn “có thể không có”.
Generic
Phần tiêu đề “Generic”Kiểu generic được đơn khoá hoá: mỗi instantiation sinh ra mã riêng, không boxing, không dispatch ảo — hiệu năng bằng viết tay.
enum Option[T] { Some(T), None }enum Result[T, E] { Ok(T), Err(E) }
fn unwrap_or(r: Result[int, str], fallback: int) -> int { return match r { Result.Ok(v) => v, Result.Err(e) => fallback }}Sở hữu
Phần tiêu đề “Sở hữu”clone() tạo một bản sao độc lập; take(x) chuyển quyền sở hữu và đánh dấu
binding gốc là moved; drop(x) giải phóng tường minh.
let xs: list[int] = [10, 20, 30]let ys: list[int] = clone(xs)ys.push(40) # chỉ ys thay đổi# xs.len() == 3, ys.len() == 4Dùng một giá trị đã move là lỗi biên dịch (use of moved value: s), nên không
có double free và không có use-after-free phải kiểm toán lúc chạy.
Lỗi không cần exception
Phần tiêu đề “Lỗi không cần exception”fn safe_div(a: int, b: int) -> Result[int, str] { if b == 0 { return Result.Err("division by zero") } return Result.Ok(a / b)}
fn chained_div(a: int, b: int, c: int) -> Result[int, str] { let q: int = safe_div(a, b)? # `?` trả về sớm khi gặp Err let r: int = safe_div(q, c)? return Result.Ok(r)}panic không dành cho lỗi dự kiến — chỉ dành cho bug lập trình. Lỗi dự kiến
(tệp không có, parse hỏng, input sai) phải đi qua Result.