Calx’s checker is stricter than Rust’s. Silent conversions and precedence slips are where exact results quietly break, in ported code most of all, so the compiler rejects them. Every rule is an error, and no flag turns one off. There are no warnings, except for deprecated APIs once there are any.
Each rule is on the page of the feature it’s about:
| Rule | Page |
|---|---|
T(x) only converts exactly. Lossy conversions are named: wrap, saturate, nearest |
Conversions |
A conversion is exact only if it is at both of usize’s widths |
Conversions |
Indexes are usize |
Types |
| A literal must fit its type, hex included. Unsigned types take no negative literal | Integers |
| A constant expression that overflows is an error | Integers |
| A constant shift count must be under the type’s width | Integers |
| Comparing with a NaN constant is an error | Floats |
Unused variables, parameters, functions, structs and constants are errors, and so are a var that never changes and a mut parameter that’s never changed |
Variables |
| No shadowing | Variables |
| Code that can never run is an error | Control flow |
A function returns with return, on every path |
Functions |
A T never becomes a T? by itself: write some(x). An optional compares only with another optional |
Optionals |
?? next to another operator needs parentheses |
Optionals |
A match covers every value, and every arm can match |
Patterns |
A capitalized name in a pattern is a variant or constant, never a binding. A record pattern names every field or ends in .. |
Patterns |
let without else needs a pattern that always matches, and let ... else and if let one that might not |
Patterns |
| A value can’t be used after it moves, or after it might have moved, and a move in a loop needs a new value before the next pass | Ownership |
| Only what a function owns can move: views, loop elements and pattern names that only look can’t. Neither can list elements or fields of views | Ownership |
| A value can’t change or move while a pattern’s names are views into it | Ownership |
What a for loop goes over can’t change or move during the loop, nor can what holds it or what’s in it |
Control flow |
In for x in mut xs, nothing reaches xs but x, to read or to change, and the loop must change x |
Control flow |
| A part of a text is a byte range that’s in order, in the text, and where runes start, checked when it runs or, for a literal, by the checker | Strings |
.copy() is only for values that own memory, and [x; n] copies only a Copy x |
Ownership |
An arena list is only a variable or a view parameter, holds only Copy elements, is made only in a let of its own, and keeps its arena from changing or moving until its block ends |
Arenas |
| Generic code is checked where it’s written and uses only what its bounds give. A call whose types can’t be worked out is an error, never a guess. Unused generic parameters are errors | Generics |
An impl matches its interface exactly, parameter names included, and has nothing else. Unused impls are errors. A name two interfaces share needs Iface.method(x) |
Interfaces |
| A type has one function of each name: its own impl can’t reuse a name its interfaces give it | Interfaces |
| Unused imports are errors, and an import can’t shadow a name | Modules |
Private fields and methods stay in their module, and a pub signature or field shows only pub types |
Modules |
| An impl goes in its type’s module or its interface’s | Modules |
An attribute that isn’t the compiler’s or an imported @attribute function is an error, wherever it’s written, rather than ignored |
Attributes |
| An attribute’s arguments are literals that fit its parameters’ exact types, and it goes only on the kind of declaration it reads. An attribute on a field must be one its declaration’s attribute lists | Attributes |
Every unsafe block does something unsafe, and none is inside another. A pointer is only a value of its own, inside unsafe |
Unsafe code |
| A C function takes and returns only C types, and has one signature wherever it’s declared | C functions |
| A function found in a library has the type asked for, down to every struct’s fields in both builds, or isn’t found. A library is from the same build of Calx as the program that opens it | Libraries |
Nothing is dropped silently
Section titled “Nothing is dropped silently”An expression on its own line must have the unit type (). A value nobody wants is discarded explicitly with _ = f(), so an ignored result is always visible.
fn three() -> i32 { return 3}
fn main() { three()error: this i32 value is unused; discard it with _ = ... _ = three()}The result of a call that can fail can’t be discarded at all, not even with _ =: its error must be passed up with try, matched, or unwrapped with ! (Errors).
Parentheses
Section titled “Parentheses”These mixtures need parentheses, since their precedence surprises people:
andwithor:a or (b and c)&,|and^with each other:(a & b) | c- a shift with
+or-:1 << (n - 1), the classic slip ??with any other operator:(a ?? 0) + 1
Comparisons don’t chain: a < b < c doesn’t compile.
fn main() { let a = true let n = 3 let _x = a or a and aerror: and and or together need parentheses let _y = 1 << n - 1error: a shift next to + or - needs parentheses}Conditions
Section titled “Conditions”A literal true or false as a condition is an error, since one branch could never run, and while true is written loop.
The rest of the strictness rules apply to features that aren’t built yet, and are in the spec.