Type system
XS uses gradual typing. Code runs fine without annotations. Add them where you want enforcement; the checker activates only on annotated code.
Gradual typing
Unannotated code is never flagged. The type checker infers types where it can and silently passes through everything it cannot determine statically.
scratch.xs
Add an annotation and the checker enforces that specific location:
let x: int = "hello"
-- error[T0001]: type mismatch: expected 'int', got 'str'
-- hint: use int() or float() to convert a string to a numberType annotations
Annotations go after a colon on bindings, after parameter names, and after -> for return types.
scratch.xs
Primitive types
int / i64 -- 64-bit signed integer (default)
i8, i16, i32 -- smaller signed integers
u8, u16, u32, u64 -- unsigned integers
float / f64 -- 64-bit float (default)
f32 -- 32-bit float
str / string -- string
bool -- boolean
char -- character
byte -- alias for u8
re -- regex
any / dyn -- any type (disables checking)
void / unit -- no value
never -- function that never returnsUse is for runtime type checks and as for casts:
scratch.xs
Composite types
scratch.xs
Checking modes
xs script.xs -- normal: check annotated code, then run
xs --check script.xs -- check only, don't execute
xs --strict script.xs -- require annotations on everything
xs --lenient script.xs -- downgrade type errors to warningsIn strict mode, every binding, parameter, and return type must be annotated:
-- xs --strict:
let x = 42
-- error[S0010]: missing type annotation for 'x' in strict mode
-- fix:
let x: int = 42Type aliases
scratch.xs
Generics
Functions, structs, and enums can declare type parameters. Parameters can have variance markers and trait bounds.
scratch.xs
-- with trait bound
fn display<T: Describe>(item: T) -> str {
return item.describe()
}
-- covariant (+T) and contravariant (-T) variance
struct Box<+T> { inner }
struct Sink<-T> { accept }