Documentation
Installation
Install XS with a single command:
curl -fsSL xslang.org/install | shThis installs the xs compiler, the VM, and all built-in tools. Supports Linux, macOS, and Windows (via WSL).
Or build from source:
git clone https://github.com/xs-lang0/xs
cd xs
make
make installHello world
fn main() {
println("hello, world")
}Run it with xs run hello.xs. The main() function is auto-called if defined.
Type system
XS has gradual typing. You can start without types and add them incrementally. The checker only enforces annotated code.
-- untyped, works fine
let x = 42
let name = "xs"
-- typed, compiler checks these
let count: int = 42
var name: str = "xs"
const MAX: i64 = 100
-- function signatures
fn add(a: int, b: int) -> int {
return a + b
}
-- generics
fn first<T>(arr: [T]) -> T {
return arr[0]
}
-- composite types
let nums: [int] = [1, 2, 3]
let pair: (int, str) = (42, "hello")
let maybe: int? = nullFunctions
Functions support overloading, default arguments, variadic params, and expression bodies.
-- expression body shorthand
fn double(x) = x * 2
-- implicit return (last expression)
fn square(x) { x * x }
-- overloading by arity
fn greet() { println("hello!") }
fn greet(name) { println("hello, {name}!") }
-- default parameters
fn connect(host, port = 8080) {
println("connecting to {host}:{port}")
}
-- variadic
fn sum(...args) {
var total = 0
for a in args { total = total + a }
return total
}
-- closures
fn make_counter() {
var count = 0
return fn() {
count = count + 1
return count
}
}
-- arrow lambdas
let inc = (x) => x + 1Pattern matching
enum Shape {
Circle(radius),
Rect(w, h),
Point
}
fn area(s) {
match s {
Shape::Circle(r) => 3.14159 * r * r
Shape::Rect(w, h) => w * h
Shape::Point => 0.0
}
}
-- guards, ranges, regex, or-patterns
fn classify(data) {
match data {
n @ 1..=10 => "small: {n}"
"a" | "e" | "i" => "vowel"
/^[0-9]+$/ => "number string"
[first, ..rest] => "list starting with {first}"
(x, y) => "pair: ({x}, {y})"
_ => "unknown"
}
}
-- string prefix patterns
fn parse_url(url) {
match url {
"https://" ++ rest => "secure: {rest}"
"http://" ++ rest => "insecure: {rest}"
_ => "unknown protocol"
}
}Effects
Algebraic effects let you perform operations without knowing how they'll be handled. The handler decides. Think of it as exceptions you can resume from.
effect Ask {
fn prompt(msg) -> str
}
fn greet() {
let name = perform Ask.prompt("name?")
return "Hello, {name}!"
}
-- the handler decides what prompt() returns
let result = handle greet() {
Ask.prompt(msg) => resume("World")
}
println(result) -- Hello, World!
-- effects with accumulation
effect Log {
fn log(msg)
}
var logs = []
handle {
perform Log.log("first")
perform Log.log("second")
} {
Log.log(msg) => {
logs.push(msg)
resume(null)
}
}
println(logs) -- ["first", "second"]Structs and traits
struct Point { x, y }
impl Point {
fn distance(self) {
return sqrt(self.x * self.x + self.y * self.y)
}
fn translate(self, dx, dy) {
return Point { x: self.x + dx, y: self.y + dy }
}
-- operator overloading
fn +(self, other) {
return Point { x: self.x + other.x, y: self.y + other.y }
}
}
-- traits
trait Describe {
fn describe(self) -> str
}
impl Describe for Point {
fn describe(self) -> str {
return "({self.x}, {self.y})"
}
}
-- struct spread/update
let p = Point { x: 10, y: 20 }
let p2 = Point { ...p, y: 30 }
-- classes with inheritance
class Animal {
name = ""
sound = "..."
fn init(self, name) {
self.name = name
}
fn speak(self) {
return "{self.name} says {self.sound}"
}
}
class Dog : Animal {
fn init(self, name) {
super.init(name)
self.sound = "woof"
}
}Concurrency
XS supports multiple concurrency models. Use whichever fits your problem.
-- spawn lightweight tasks
spawn { println("in background") }
-- async/await
async fn compute(x) {
return x * 2
}
let r = await compute(21) -- 42
-- channels
let ch = channel()
spawn {
ch.send("ping")
ch.send("pong")
}
println(ch.recv()) -- ping
println(ch.recv()) -- pong
-- nurseries (structured concurrency)
var results = []
nursery {
spawn { results.push("a") }
spawn { results.push("b") }
spawn { results.push("c") }
}
-- all tasks complete before we get here
-- actors
actor Cache {
var data = #{}
fn set(key, val) { data[key] = val }
fn get(key) { return data[key] }
}
let c = spawn Cache
c.set("x", 42)
println(c.get("x")) -- 42Interop
Inline C for performance-critical code. Use xs transpile to compile to C, JS, or WASM.
fn fast_hash(data) {
inline c {
uint64_t h = 0x525201;
const char *s = xs_to_cstr(args[0]);
while (*s) h = h * 31 + *s++;
xs_return_int(h);
}
return 0 -- fallback for interpreter mode
}xs transpile --target c main.xs
xs transpile --target js main.xs
xs transpile --target wasm main.xsTooling
xs run main.xs -- run a file
xs build main.xs -- compile to binary
xs test -- run tests
xs fmt -- format code
xs lint -- lint code
xs lsp -- start language server
xs debug main.xs -- start debugger
xs --check main.xs -- type check only
xs --strict main.xs -- require all annotations