Items
Items are the top-level declarations in a Joule program.
Functions
fn name(param: Type, param2: Type) -> ReturnType {
body
}
Visibility
pub fn public_function() { } // visible outside module
fn private_function() { } // module-private (default)
Parameters
Parameters are passed by value (move) by default:
fn process(data: Vec<u8>) {
// data is moved into this function
}
Use references for borrowing:
fn inspect(data: &Vec<u8>) {
// data is borrowed immutably
}
fn modify(data: &mut Vec<u8>) {
// data is borrowed mutably
}
Self Parameter
Methods take self as their first parameter:
impl Point {
fn distance(self) -> f64 { } // takes ownership
fn inspect(self) -> f64 { } // immutable self
fn translate(mut self, dx: f64) { } // mutable self
}
Generic Functions
fn first<T>(items: Vec<T>) -> Option<T> {
if items.len() > 0 {
Option::Some(items[0])
} else {
Option::None
}
}
Extern Functions
Functions implemented outside Joule (FFI):
extern fn sqrt(x: f64) -> f64;
extern fn malloc(size: usize) -> *mut u8;
Const Functions
Functions that can be evaluated at compile time are declared with const fn:
const fn max(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
const fn factorial(n: i32) -> i32 {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
// Use at compile time
const MAX_SIZE: i32 = max(100, 200);
const FACT_10: i32 = factorial(10);
Const Function Restrictions
const fn bodies are restricted to operations the compiler can evaluate:
- Arithmetic operations on primitive types
- Control flow (
if,match, recursion) - Local variable bindings
- Calling other
const fnfunctions
The following are not allowed in const fn:
- Heap allocation (
Vec::new(),Box::new()) - I/O operations
- Mutable static state
- Non-const function calls
Comptime Blocks
For more complex compile-time computation, use comptime blocks:
comptime {
let table = generate_sin_table(1024);
}
// table is available as a compile-time constant
fn fast_sin(x: f64) -> f64 {
let index = (x * 1024.0 / TAU) as usize;
table[index % 1024]
}
Comptime blocks execute during compilation and make their results available as constants in runtime code. The HIR const evaluator handles arithmetic, control flow, and function calls within comptime blocks.
Structs
Named product types with fields:
pub struct Point {
pub x: f64,
pub y: f64,
}
Field Visibility
Fields are private by default. Use pub to make them accessible:
pub struct Config {
pub name: String, // public
secret_key: String, // private
}
Generic Structs
pub struct Pair<A, B> {
pub first: A,
pub second: B,
}
Enums
Sum types (tagged unions) with variants:
pub enum Color {
Red,
Green,
Blue,
}
Variants with Data
pub enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Point,
}
Tuple Variants
pub enum Option<T> {
Some(T),
None,
}
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Generic Enums
pub enum Either<L, R> {
Left(L),
Right(R),
}
Impl Blocks
Associate methods with a type:
impl Point {
// Associated function (no self)
pub fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
// Method (takes self)
pub fn distance(self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
Multiple impl blocks are allowed for the same type:
impl Point {
pub fn new(x: f64, y: f64) -> Point { Point { x, y } }
}
impl Point {
pub fn translate(mut self, dx: f64, dy: f64) {
self.x = self.x + dx;
self.y = self.y + dy;
}
}
Traits
Define shared behavior:
pub trait Display {
fn to_string(self) -> String;
}
pub trait Clone {
fn clone(self) -> Self;
}
Trait Implementation
impl Display for Point {
fn to_string(self) -> String {
"(" + self.x.to_string() + ", " + self.y.to_string() + ")"
}
}
Trait Bounds
fn print_all<T: Display>(items: Vec<T>) {
for item in items {
println!("{}", item.to_string());
}
}
Dynamic Dispatch
Use dyn Trait for runtime polymorphism:
fn print_shape(shape: &dyn Display) {
println!("{}", shape.to_string());
}
Type Aliases
pub type Token = Spanned<TokenKind>;
pub type ParseResult<T> = Result<T, ParseError>;
Modules
Module Declarations
Modules organize code into separate files. The mod keyword declares a module:
mod lexer; // loads from lexer.joule or lexer/mod.joule
mod parser;
mod typeck;
File Resolution
When the compiler encounters mod foo;, it searches for:
foo.joulein the same directory as the current filefoo/mod.joulefor modules with sub-modules
Public Module Re-exports
pub mod utils; // re-exports utils module to parent
Inline Modules
Modules can be defined inline within a file:
mod helpers {
pub fn clamp(x: i32, lo: i32, hi: i32) -> i32 {
if x < lo { lo } else if x > hi { hi } else { x }
}
}
// Use items from inline module
let clamped = helpers::clamp(value, 0, 100);
Visibility
pub mod public_module { }
mod private_module { }
Use Declarations
Import items into scope:
// Import specific items
use crate::ast::{File, AstItem, Visibility};
// Import all items from a module
use crate::prelude::*;
// Standard library imports
use std::collections::HashMap;
use std::math::*;
// Import with alias
use crate::ast::File as AstFile;
Stdlib Path
The --stdlib-path CLI flag specifies the location of the standard library. The builtin registry includes modules for math, statistics, and compute:
use std::math::*; // sin, cos, sqrt, etc.
use std::statistics::*; // mean, median, std_dev
Let Statements
Variable bindings:
let x = 42; // immutable, type inferred
let y: f64 = 3.14; // immutable, explicit type
let mut z = 0; // mutable
let (a, b) = (1, 2); // destructuring