Examples
Complete, runnable programs demonstrating Sun. Every example below is compiled
and executed in CI, and the source shown here is the exact source in the
examples/ (opens in a new tab) folder.
Hello
The smallest Sun program: main prints a greeting via the stdlib's println.
export SUN_PATH=".:../../build"
sun --compile -o main main.sun
./main
# Hello, Sun!Source
using sun;
function main() void {
println("Hello, Sun!");
}
manifest {
moons: ["stdlib.moon"]
}Classes
Classes are stack-allocated value types with methods and an init constructor.
Class values are passed by ref (Sun never copies them implicitly). This Point
computes the Manhattan distance between two points.
./build.sh
./mainSource
using sun;
// Classes are stack-allocated value types. They are passed by `ref` and
// carry methods, including an `init` constructor.
class Point {
public var x: i32;
public var y: i32;
public function init(x_: i32, y_: i32) {
this.x = x_;
this.y = y_;
}
// Manhattan distance to another point. Class arguments are passed by ref.
public function manhattan(other: ref Point) i32 {
var dx = this.x - other.x;
if (dx < 0) {
dx = -dx;
}
var dy = this.y - other.y;
if (dy < 0) {
dy = -dy;
}
return dx + dy;
}
}
function main() i32 {
var origin = Point(0, 0);
var p = Point(3, 4);
var dist = p.manhattan(origin); // 7
println(dist);
return 0;
}
manifest {
moons: ["stdlib.moon"]
}Interfaces
Interfaces declare behaviour that classes can implements. A function taking
ref Drawable dispatches dynamically to the concrete type at runtime, so
render draws a Circle or a Square without knowing which it holds.
./build.sh
./mainSource
using sun;
// An interface declares behaviour that classes can implement. Functions can
// accept `ref Interface` and dispatch dynamically to the concrete type.
interface Drawable {
public function draw() void;
}
class Circle implements Drawable {
public var radius: f64;
public function init(r: f64) {
this.radius = r;
}
public function draw() void {
println("Drawing circle with radius:");
println(this.radius);
}
}
class Square implements Drawable {
public var side: f64;
public function init(s: f64) {
this.side = s;
}
public function draw() void {
println("Drawing square with side:");
println(this.side);
}
}
// `shape` is dispatched dynamically based on its concrete type.
function render(shape: ref Drawable) void {
shape.draw();
}
function main() i32 {
var c = Circle(5.0);
var s = Square(10.0);
render(c);
render(s);
return 0;
}
manifest {
moons: ["stdlib.moon"]
}Error Handling
Functions that can fail declare an error type with the , IError suffix and
signal failure with throw. Errors are real exceptions: a throw unwinds the
stack to the nearest matching catch. Any class implementing IError can be
thrown — here the standard library's DivisionByZeroError.
./build.sh
./mainSource
using sun;
// A function that can fail declares an error type after its return type with
// the `, IError` suffix, and signals failure with `throw`. The thrown value is
// any class implementing `IError` (the standard library provides several).
function divide(a: i32, b: i32) i32, IError {
if (b == 0) {
throw DivisionByZeroError();
}
return a / b;
}
function main() i32 {
// A successful call needs no special handling.
try {
var ok = divide(10, 2); // 5
println(ok);
} catch (e: IError) {
println("unexpected error");
}
// A failing call unwinds to the nearest matching catch.
try {
var bad = divide(10, 0); // throws DivisionByZeroError
println(bad);
} catch (e: IError) {
println("caught division by zero");
}
return 0;
}
manifest {
moons: ["stdlib.moon"]
}Class Destructors
Sun runs a class's deinit method automatically when a value goes out of scope,
giving deterministic cleanup with no garbage collector. Here foo is destroyed
at the end of main.
./build.sh
./mainSource
using sun;
class Foo {
public function init() {}
function deinit() void {
println("Foo deinit was called.");
}
}
function main() void {
var foo = Foo();
println("Exiting main, foo will go out of scope and deinit will be called.");
}
manifest {
moons: ["stdlib.moon"]
}Lambdas & Closures
Lambdas are anonymous functions that capture variables from their enclosing
scope. Here scale closes over multiplier and is applied to each value from
1 to 5, summing to 150.
./build.sh
./mainSource
using sun;
function main() i32 {
var multiplier: i32 = 10;
// A lambda captures `multiplier` from the enclosing scope by closure.
var scale = lambda (x: i32) i32 {
return x * multiplier;
};
var total: i32 = 0;
for (var i: i32 = 1; i <= 5; i = i + 1) {
total = total + scale(i); // 10 + 20 + 30 + 40 + 50
}
println(total); // 150
return 0;
}
manifest {
moons: ["stdlib.moon"]
}Modules & Transitive Moons
Sun compiles reusable libraries into .moon files. Dependencies are transitive
at the bitcode level but opaque at the symbol level: main sees moon1, but not
the moon2/moon3 symbols that moon1 pulls in. The chain
main -> moon1 -> moon2 -> moon3 computes 1 + 2 + 3 = 6.
The compiled moon1.moon file contains the bitcode of moon2 and moon3.
./build.sh
./mainSource
using sun;
function main() void {
println(moon1.moon1());
}
manifest {
moons: ["stdlib.moon", "moon1.moon"]
}public module moon1 {
public function moon1() i32 {
return 1 + moon2.moon2();
}
}
manifest {
suns: []
moons: [{ path: "moon2.moon"}]
}public module moon2 {
public function moon2() i32 {
return 2 + moon3.moon3();
}
}
manifest {
suns: []
moons: ["moon3.moon"]
}public module moon3 {
public function moon3() i32 {
return 3;
}
}
manifest {
suns: []
moons: []
}TCP Connection
A raw TCP client/server built on the standard library's TcpListener and
TcpStream. The listener binds to 127.0.0.1:8080 and prints whatever it
receives; the talker connects and sends a message.
Build both executables:
./build.shRun the listener first, then the talker in a second terminal:
./listener # terminal 1
./talker # terminal 2Source
// Listener: accepts one connection and prints received message
using sun;
function main() i32 {
var listener = TcpListener();
listener.bind_loopback(8080);
listener.listen(1);
println("Listening on 127.0.0.1:8080...");
var client = listener.accept();
println("Client connected");
var alloc = make_heap_allocator();
var buf = ContiguousBuffer<u8>(alloc, 256);
while (true) {
var n = client.recv(buf);
if (n <= 0) {
println("Connection closed");
break;
}
try {
var str = String(alloc, buf, n);
println(str);
} catch (e: IError) {
println("Failed to create string");
}
}
client.close();
listener.close();
return 0;
}
manifest {
moons: ["stdlib.moon"]
}// Talker: connects and sends a message
using sun;
function main() i32 {
var stream = TcpStream();
stream.connect_local(8080);
println("Connected to 127.0.0.1:8080");
stream.send_str("Hello from talker!");
println("Message sent");
stream.close();
return 0;
}
manifest {
moons: ["stdlib.moon"]
}HTTP Server
A webpage served with the standard library's HttpServer. The server owns
the accept loop and all HTTP framing (request parsing, status line,
Content-Length); the handler lambda only inspects HttpRequest and fills
in HttpResponse.
Build:
./build.shRun and open http://127.0.0.1:8080 (opens in a new tab) in a browser:
./serverOr verify from another terminal:
curl -i http://127.0.0.1:8080/ # 200 with the page
curl -i http://127.0.0.1:8080/missing # 404Source
// HTTP server: serves a small webpage on http://127.0.0.1:8080
using sun;
function main() i32 {
var alloc = make_heap_allocator();
var server = HttpServer(alloc);
try {
server.bind_loopback(8080);
} catch (e: IError) {
println("Failed to bind 127.0.0.1:8080 (port in use?)");
return 1;
}
println("Serving on http://127.0.0.1:8080");
server.serve(
lambda (req: ref HttpRequest, resp: ref HttpResponse) void {
if (req.path.equals_literal("/")) {
resp.set_body("<html><body><h1>Hello from Sun!</h1><p>This page is served by the Sun standard library.</p></body></html>");
} else {
resp.set_status(404);
resp.set_body("<html><body><h1>404 Not Found</h1></body></html>");
}
}
);
return 0;
}
manifest {
moons: ["stdlib.moon"]
}