Examples

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.

Source

main.sun
using std;
 
/** Runs the example that prints a greeting. */
function main() void {
  println("Hello, Sun!");
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

sun --compile -o main main.sun
./main
# Hello, Sun!

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.

Source

main.sun
using std;
 
/**
 * 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;
 
  /** Initializes the fixture fields used by this example or regression test. */
  init(x_: i32, y_: i32) {
    this.x = x_;
    this.y = y_;
  }
 
  /**
   * Manhattan distance to another point. Class arguments are passed by ref.
   */
  public method 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;
  }
}
 
/** Runs the example that constructs and uses class values. */
function main() i32 {
  var origin = Point(0, 0);
  var p = Point(3, 4);
 
  var dist = p.manhattan(origin);  // 7
  println(dist);
  return 0;
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main

Build dependency from Git source

Fetches a pinned revision of sun_serve (opens in a new tab), builds the Moon library selected from its sun-config.json, and links a program that checks HTTP status helpers and polling results, then prints the library version and OK. The pinned revision uses ServerResult<T>: the consumer propagates setup and polling failures with prefix try, handles an expected invalid-descriptor error with match, and reports unexpected errors from main.

Requires x86_64 Linux, Git, Sun with stdlib.moon, and musl archives libz.a, libssl.a, and libcrypto.a. Run scripts/fetch-openssl.sh to install these under third_party/openssl/x86_64-linux-musl, the config default. The Docker image supplies them through SUN_EXAMPLE_NATIVE_LIBS; set that variable to use another directory when running build.sh. The fetched repository stays unchanged. Explicit --path-var arguments override the build script and config defaults. Build and test scripts explicitly skip unsupported platforms.

The dependency omits entrypoint because this revision declares exactly one library. Its source path and output filename come from its own config. Only that library and its dependencies are built; its programs and tests are skipped. The consumer imports "$SUN_SERVE/sun_serve.moon", which resolves to the isolated build cache. No prebuilt sun_serve.moon or manual checkout is needed.

The consumer's sun_path takes precedence over the dependency's search paths. This example includes ../../build, so both sun_serve and the example use your workspace's build/stdlib.moon when present. Build it from your local sources:

build/sun --emit-moon -o build/stdlib.moon stdlib/stdlib.sun

For another Sun checkout, set sun_path to its build directory instead.

From the workspace root, starting without the native archives:

bash scripts/fetch-openssl.sh
examples/100-git_library/build.sh
bash examples/100-git_library/test.sh

Unchanged builds skip compilation. version accepts a branch, tag, or commit; the example pins a commit for repeatable builds. Branches and tags remain cached until refreshed:

examples/100-git_library/build.sh --refresh-sources

Sources are cached in ~/.sun/cache/git, overridable with SUN_GIT_CACHE. For SSH, set git to git@github.com:namo-robotics/sun_serve.git or ssh://git@github.com/namo-robotics/sun_serve.git; both use Git's normal SSH credentials. To use a precompiled Moon, replace the SUN_SERVE dependency with a moon descriptor whose filename is sun_serve.moon; main.sun stays unchanged.

To use the direct-entrypoint form instead, replace "config": "sun-config.json" with "path": "src/sun_serve.sun" in the SUN_SERVE dependency. This builds the source using this example's configuration and ignores the repository's config. The manifest and build commands stay the same; both forms produce $SUN_SERVE/sun_serve.moon.

Source

main.sun
manifest {
    libraries: ["stdlib.moon", "$SUN_SERVE/sun_serve.moon"]
}
 
using std;
using sun_serve;
 
/** Checks polling results and HTTP helpers from the imported library. */
function check_library() ServerResult<i32> {
  var alloc = make_heap_allocator();
  var layout = try probe_epoll_layout(alloc);
  var poller = try Epoll(alloc, layout, 1);
  if (try poller.wait(0)) != 0 {
    return ServerResult.Ok(3);
  }
  // An invalid descriptor must produce an owned error across the Moon boundary.
  match poller.add(-1, 0, 0) {
    ServerResult.Ok => {
      return ServerResult.Ok(4);
    },
    ServerResult.Error(error) => {
      if error.message().length() == 0 {
        return ServerResult.Ok(5);
      }
    },
    (error: const ref IError) => {
      eprintln(error.message());
      return ServerResult.Ok(6);
    }
  };
  if status_has_no_body(200) {
    return ServerResult.Ok(1);
  }
  if status_has_no_body(204) {
    println(version());
    println(reason_phrase(200));
    return ServerResult.Ok(0);
  }
  return ServerResult.Ok(2);
}
 
/** Reports unexpected library failures and returns the integration status. */
function main() i32 {
  return match check_library() {
    ServerResult.Ok(status) => status,
    (error: const ref IError) => {
      eprintln(error.message());
      return 7;
    }
  };
}

GPU matrix example

Build with -DSUN_ENABLE_CUDA=ON, then run from the repository root:

build/sun --lib-path build -lcublas -lcudart -lpthread examples/110-gpu-matrices/main.sun

For toolkits outside the system library search path, add -L with the toolkit's library directory and include that directory in LD_LIBRARY_PATH.

The example uploads [[1, 2], [3, 4]] twice, multiplies the GPU matrices with try (a * b), and downloads the product. The result is [[7, 10], [15, 22]], printed one element per line. CUDA errors produce a nonzero exit status.

Source

main.sun
/** Multiplies two matrices on the GPU and prints the result. */
manifest { libraries: ["stdlib.moon", "cuda.moon"] }
using std;
using cuda;
 
/** Uploads a matrix twice, multiplies on the GPU, and downloads the product. */
function run() GpuResult<void> {
  var alloc = make_heap_allocator();
  var gpu = try open_device(0);
  var a_cpu = Matrix<f32>([[1.0f32, 2.0f32], [3.0f32, 4.0f32]], alloc);
  var b_cpu = Matrix<f32>([[1.0f32, 2.0f32], [3.0f32, 4.0f32]], alloc);
  var a = try gpu.upload(a_cpu);
  var b = try gpu.upload(b_cpu);
  var c = try (a * b);
  var c_cpu = Matrix<f32>(alloc, [2, 2]);
  try c.copy_to_host(c_cpu);
  println(c_cpu[0, 0]);
  println(c_cpu[0, 1]);
  println(c_cpu[1, 0]);
  println(c_cpu[1, 1]);
  return GpuResult.Ok;
}
 
/** Prints failures and returns a failing process status. */
function main() i32 {
  return match run() {
    (error: const ref IError) => {
      println(error.message());
      1;
    },
    _ => 0
  };
}

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.

Source

main.sun
using std;
 
/**
 * An interface declares behaviour that classes can implement. Functions can
 * accept `ref Interface` and dispatch dynamically to the concrete type.
 */
interface Drawable {
  /** Prints a description of this shape through its drawing interface. */
  public method draw() void;
}
 
/** A drawable circle used to demonstrate interface dispatch. */
class Circle implements Drawable {
  public var radius: f64;
 
  /** Initializes the fixture fields used by this example or regression test. */
  init(r: f64) {
    this.radius = r;
  }
 
  /** Prints a description of this shape through its drawing interface. */
  public method draw() void {
    println("Drawing circle with radius:");
    println(this.radius);
  }
}
 
/** A drawable square used to demonstrate interface dispatch. */
class Square implements Drawable {
  public var side: f64;
 
  /** Initializes the fixture fields used by this example or regression test. */
  init(s: f64) {
    this.side = s;
  }
 
  /** Prints a description of this shape through its drawing interface. */
  public method 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();
}
 
/** Runs the example that dispatches drawing methods through an interface. */
function main() i32 {
  var c = Circle(5.0);
  var s = Square(10.0);
 
  render(c);
  render(s);
  return 0;
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main

Error Handling

Functions that can fail return an enum. The first variant holds success; other variants own concrete errors. Use try expression to propagate a failure, or match to handle it. One typed interface arm can handle several error variants without erasing the owned payload types.

Source

main.sun
using std;
 
/** Returns either a quotient or a concrete arithmetic failure. */
function divide(a: i32, b: i32) ArithmeticResult<i32> {
  if (b == 0) {
    return ArithmeticResult.DivisionByZero(DivisionByZeroError());
  }
  if (a == -2147483648 and b == -1) {
    return ArithmeticResult.Overflow(OverflowError());
  }
  return ArithmeticResult.Ok(a / b);
}
 
/** Handles an arithmetic result without requiring one arm per error. */
function show(a: i32, b: i32) void {
  match divide(a, b) {
    ArithmeticResult.Ok(value) => println(value),
    (error: const ref IError) => println(error.message())
  };
}
 
/** Exercises successful division and a recoverable zero divisor. */
function main() i32 {
  show(10, 2);
  show(10, 0);
  return 0;
}
 
manifest {
  libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main

Testing

Tests are declared with test_function and live either next to the code they exercise — where module-scoped privacy lets them call private helpers — or in test-only files listed under test_files: in the manifest. Assertions come from std.test; try propagates failed assertions and drops local fixtures, so their deinit teardown still runs when a test fails.

Compiling with -c produces two binaries: main, with every test stripped, and main_test, which runs the tests — in parallel by default, or one after another with --test-sequential. During development, sun test main.sun runs them under the JIT without building anything.

Source

main.sun
using std;
 
// The code under test: distance helpers in a module. `half` is private, but
// privacy is module-scoped, so tests declared inside the module reach it.
/** Groups declarations used by this compiler regression test. */
module distance {
  /** Returns half of the supplied value for the testing example. */
  function half(n: i64) i64 {
    return n / 2;
  }
 
  /** Returns the midpoint of two values for the testing example. */
  public function midpoint(a: i64, b: i64) i64 {
    return a + distance.half(b - a);
  }
 
  /**
   * A test is a function declared with `test_function`: no parameters, no
   * return annotation, and propagates failed assertions with `try`.
   */
  test_function halves() {
    try std.test.assert_eq(distance.half(10), 5);
  }
 
  /** Checks finds the midpoint. */
  test_function findsTheMidpoint() {
    try std.test.assert_eq(distance.midpoint(2, 10), 6);
  }
}
 
/**
 * A fixture is an ordinary class: init is the setup, deinit the teardown.
 * Propagating a failed assertion drops locals, so teardown runs whether
 * the test passes or fails.
 */
class ScratchBuffer {
  public var data: Vec<i64>;
 
  /** Initializes the fixture fields used by this example or regression test. */
  init() {
    var alloc = make_heap_allocator();
    this.data = Vec<i64>(alloc, 4);
    this.data.push(1);
  }
 
  /** Records fixture teardown so the test can check cleanup behavior. */
  deinit() {
    // The Vec releases itself; a real fixture would remove temp files,
    // close connections, and so on.
  }
}
 
/** Checks starts with one entry. */
test_function startsWithOneEntry() {
  var fx = ScratchBuffer();
  try std.test.assert_eq(fx.data.size(), 1);
}
 
/** Runs the example that uses functions exercised by the example tests. */
function main() i32 {
  // The production binary never contains the tests above.
  var m = distance.midpoint(2, 10);
  println(`midpoint(2, 10) = ${m}`);
  return 0;
}
 
manifest {
    test_files: ["more_tests.sun"]
    libraries: ["stdlib.moon"]
}
more_tests.sun
using std;
 
// A test-only file, listed under `test_files:` in the manifest. It is
// loaded only when compiling tests, and it reopens `module distance` so its
// tests reach the module's private items too.
/** Groups declarations used by this compiler regression test. */
module distance {
  /** Checks midpoint of equal points is that point. */
  test_function midpointOfEqualPointsIsThatPoint() {
    try std.test.assert_eq(distance.midpoint(4, 4), 4);
  }
}

Build and run

./build.sh
./main            # the program; contains no test code
./main_test       # runs the 4 tests, exit 0 when all pass
sun test main.sun # same tests, JIT

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.

Source

main.sun
using std;
 
/** Demonstrates a class whose destructor runs when its value leaves scope. */
class Foo {
  /** Creates an empty fixture value for this example or regression test. */
  init() {}
 
  /** Prints a message to demonstrate when scope cleanup runs. */
  deinit() {
    println("Foo deinit was called.");
  }
}
 
/** Runs the example that demonstrates class cleanup at scope exit. */
function main() void {
  var foo = Foo();
  println("Exiting main, foo will go out of scope and deinit will be called.");
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main

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.

Source

main.sun
using std;
 
/** Runs the example that creates and invokes captured functions. */
function main() i32 {
  var multiplier: i32 = 10;
 
  // A lambda captures `multiplier` from the enclosing scope by closure.
  var scale = (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 {
    libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main

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. Native archives travel the same way: had moon3 carried a .a under archives:, moon1.moon would carry it too, and main would link it without ever naming moon3.moon.

Source

main.sun
using std;
 
/** Runs the example that calls through transitive compiled-library dependencies. */
function main() void {
  println(moon1.moon1());
}
 
manifest {
    libraries: ["stdlib.moon", "moon1.moon"]
}
moon1/entry.sun
/** Returns a value obtained through the example's first compiled-library dependency. */
public module moon1 {
  /** Returns a value obtained through the example's first compiled-library dependency. */
  public function moon1() i32 {
    return 1 + moon2.moon2();
  }
}
 
manifest {
    source_files: []
    libraries: [{ path: "moon2.moon"}]
}
moon2/entry.sun
/** Returns a value obtained through the example's second compiled-library dependency. */
public module moon2 {
  /** Returns a value obtained through the example's second compiled-library dependency. */
  public function moon2() i32 {
    return 2 + moon3.moon3();
  }
}
 
manifest {
    source_files: []
    libraries: ["moon3.moon"]
}
moon3/entry.sun
/** Supplies the value exported by the example's transitive library dependency. */
public module moon3 {
  /** Supplies the value exported by the example's transitive library dependency. */
  public function moon3() i32 {
    return 3;
  }
}
 
manifest {
    source_files: []
    libraries: []
}

Build and run

./build.sh
./main

Debug artifacts for a library and executable

This example builds a tiny Moon library exposing life.answer() and an executable that calls it. The executable exits with status zero when the library returns the expected value. It needs no standard library.

The sun-config.json declares both build products, so one sun -c invocation builds the library bundle and then the executable:

sun-config.json
{
  "root": true,
  "sun_path": ["."],
  "entrypoints": [
    { "path": "life.sun", "type": "library", "output_name": "life.moon" },
    { "path": "main.sun", "type": "binary", "output_name": "main" }
  ]
}

Run from the repository root:

SUN_BIN=./build/sun bash examples/65-debug/build.sh
bash examples/65-debug/test.sh
python3 -m json.tool examples/65-debug/life_debug/moon.json

If sun is on your PATH, omit SUN_BIN=./build/sun. The script runs sun -c --debug --no-test sun-config.json; --debug and --no-test are build-mode flags rather than project settings, so they stay on the command line.

Both builds use --debug and produce ast.dot, scope_tree.html, and ir.ll in life_debug/ and main_debug/. Open the HTML files in a browser to inspect scopes, or read ir.ll for the compiled code.

The library also produces life_debug/moon.json, containing its exported metadata, format version, and locations of binary payloads. Find life in modules[].metadata.module_name and answer in the module's functions. Declaration keys are opaque strings. Source keys are generated as $<hash>$_<ordinal>; specialized declarations use deterministic hashes. Binary payload bytes are omitted. The .moon file remains the binary bundle used by the executable.

Source

life.sun
public module life {
  public function answer() i32 {
    return 42;
  }
}
main.sun
/** Exits successfully when the compiled library returns the expected value. */
function main() i32 {
  return life.answer() - 42;
}
 
manifest {
  libraries: ["life.moon"]
}

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.

Source

listener.sun
// Listener: accepts one connection and prints received message
using std;
 
/** Performs the operation and propagates returned failures. */
function run() Result<void> {
  var alloc = make_heap_allocator();
  var buf = ContiguousBuffer<u8>(alloc, 256);
 
  var listener = TcpListener();
  try listener.bind_loopback(8080);
  try listener.listen(1);
  println("Listening on 127.0.0.1:8080...");
 
  var client = try listener.accept();
  println("Client connected");
 
  while (true) {
    var n = try client.recv(buf);
    if (n <= 0) {
      println("Connection closed");
      break;
    }
    var str = try String(alloc, buf, n);
    println(str);
  }
 
  client.close();
  listener.close();
 
  return Result.Ok;
}
 
/** Reports errors and sets the process exit code. */
function main() i32 {
  return match run() {
    (e: const ref IError) => {
      println(e.message());
      1;
    },
    _ => 0
  };
}
 
manifest {
    libraries: ["stdlib.moon"]
}
talker.sun
// Talker: connects and sends a message
using std;
 
/** Performs the operation and propagates returned failures. */
function run() Result<void> {
  var stream = TcpStream();
  try stream.connect_local(8080);
  println("Connected to 127.0.0.1:8080");
 
  try stream.send_str("Hello from talker!");
  println("Message sent");
 
  stream.close();
 
  return Result.Ok;
}
 
/** Reports errors and sets the process exit code. */
function main() i32 {
  return match run() {
    (e: const ref IError) => {
      println(e.message());
      1;
    },
    _ => 0
  };
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

Build both executables:

./build.sh

Run the listener first, then the talker in a second terminal:

./listener   # terminal 1
./talker     # terminal 2

UDP Sockets

A minimal UDP pair built on the standard library's UdpSocket. The receiver binds 127.0.0.1:9090 and prints the first datagram it gets, along with the sender's address; the sender binds an ephemeral port, sends one message and exits.

Unlike TCP there is no connection: each send_to is one self-contained datagram, and recv_from reports who sent it alongside the bytes.

Source

receiver.sun
// Receiver: binds a UDP port and prints one datagram and its sender
using std;
 
/** Performs the operation and propagates returned failures. */
function run() Result<void> {
  var alloc = make_heap_allocator();
  var buf = ContiguousBuffer<u8>(alloc, 256);
 
  var socket = UdpSocket();
  try socket.bind(ipv4_loopback(), 9090);
  println("Waiting on 127.0.0.1:9090...");
 
  var datagram = try socket.recv_from(buf);
  var text = try String(alloc, buf, datagram.length());
  println(text);
 
  var from = datagram.addr();
  println("From address:");
  println(from.to_string(alloc));
 
  socket.close();
 
  return Result.Ok;
}
 
/** Reports errors and sets the process exit code. */
function main() i32 {
  return match run() {
    (e: const ref IError) => {
      println(e.message());
      1;
    },
    _ => 0
  };
}
 
manifest {
    libraries: ["stdlib.moon"]
}
sender.sun
// Sender: sends one datagram to the receiver
using std;
 
/** Performs the operation and propagates returned failures. */
function run() Result<void> {
  var socket = UdpSocket();
  try socket.bind(ipv4_any(), 0);  // port 0: the OS picks a free port
  try socket.send_str_to("Hello from sender!", ipv4_loopback(), 9090);
  println("Datagram sent");
 
  socket.close();
 
  return Result.Ok;
}
 
/** Reports errors and sets the process exit code. */
function main() i32 {
  return match run() {
    (e: const ref IError) => {
      println(e.message());
      1;
    },
    _ => 0
  };
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

Build both executables:

./build.sh

Run the receiver first, then the sender in a second terminal:

./receiver   # terminal 1
./sender     # terminal 2

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.

Source

server.sun
// HTTP server: serves a small webpage on http://127.0.0.1:8080
using std;
 
/** Runs the example that serves HTTP requests. */
function main() i32 {
  var alloc = make_heap_allocator();
  var server = HttpServer(alloc);
  match server.bind_loopback(8080) {
    SystemResult.Ok => {},
    SystemResult.Error(e) => {
      println(e.message());
      return 1;
    }
  };
  println("Serving on http://127.0.0.1:8080");
 
  server.serve(
    (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 {
    libraries: ["stdlib.moon"]
}

Build and run

Build:

./build.sh

Run and open http://127.0.0.1:8080 (opens in a new tab) in a browser:

./server

Or 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  # 404

HTTPS Client

An HTTPS request with HttpsClient from the tls bundle. The client opens a TLS connection, sends an HTTP/1.1 request and parses the response, decoding either a Content-Length or a chunked body.

using std;
using tls;
 
var client = HttpsClient(alloc);
var host = String(alloc, "example.com");
var response = client.get(host, "/");

Certificates are verified by default: the chain must validate against the system CA store and the certificate must match the hostname, otherwise connect returns a TLS error. Point SSL_CERT_FILE at a PEM file to trust a private CA instead.

tls.moon carries its own static OpenSSL, so nothing here needs -lssl, -lcrypto, or OpenSSL installed on the machine.

Source

client.sun
// An HTTPS GET with the tls bundle's HttpsClient.
//
// The certificate is verified against the system CA store and checked
// against the hostname; a bad certificate returns an error.
 
using std;
using tls;
 
/** Carries request, system, or response access failures. */
enum RequestResult { Ok, Tls(tls.TlsError), System(Error), Bounds(IndexOutOfBoundsError) }
 
/** Performs the operation and propagates returned failures. */
function run() RequestResult {
  var alloc = make_heap_allocator();
  var client = HttpsClient(alloc);
  var host = String(alloc, "example.com");
 
  var response = try client.get(host, "/");
 
  print("status: ");
  println(response.status);
  print("body bytes: ");
  println(response.body.length());
 
  // Print the first line of the body
  var end: i64 = 0;
  while (end < response.body.length() and (try response.body.at(end)) != b'\n') {
    end = end + 1;
  }
  var first_line = try response.body.substr(alloc, 0, end);
  println(first_line);
 
  return RequestResult.Ok;
}
 
/** Reports errors and sets the process exit code. */
function main() i32 {
  return match run() {
    (e: const ref IError) => {
      println(e.message());
      1;
    },
    _ => 0
  };
}
 
manifest {
    libraries: ["stdlib.moon", "tls.moon"]
}

Build and run

Building it yourself requires the bundle, which the normal build produces once ./scripts/fetch-openssl.sh (Linux) or ./scripts/build-openssl-macos.sh (macOS) has supplied the archives.

./build.sh
./client
ldd client        # Linux: not a dynamic executable
otool -L client   # macOS: system libraries only, no OpenSSL

On Linux the binary is fully static; macOS binaries always link the system libraries, but no OpenSSL among them — it is inside the binary.

Producer & Consumer

Two threads and one queue. The producer pushes ten squares onto a shared Queue<i64>; the consumer takes them off and adds them up. The consumer receives all ten and their sum is 385, whichever order the threads happen to run in.

Shared<T> puts the queue in a heap box next to a reference count and a mutex. clone() adds an owner, lock() returns a guard that holds the mutex until it goes out of scope, and the last owner to be dropped frees the queue. Sun never copies a class implicitly, so a thread cannot reach the queue by accident — every extra owner is a visible clone().

spawn takes what the thread should work on as arguments and moves them in, so a thread body does not have to be a closure over the surrounding frame. That is why produce and consume are plain lambdas at global scope taking a Shared<Queue<i64>> parameter: each spawn(produce, queue.clone()) hands one clone to one thread, main cannot use that clone afterwards, and the thread releases it when it finishes.

Capturing still works: spawn([ref q]() => i32 { … }) borrows q for one thread. But two threads cannot borrow the same handle mutably — the second is rejected with cannot borrow 'q' as mutable because it is already borrowed — so passing each thread its own clone is both simpler and what makes two of them legal.

Where the lock is taken decides how much the threads get in each other's way. take_one holds it only for the pop(), and has released it by the time it returns, so the consumer can wait for an empty queue without blocking the producer. Sleeping while still holding the guard would serialise the two threads completely.

The consumer stops after ten items because it knows how many to expect. A queue that had to signal "no more items are coming" would need a flag of its own alongside the items, read under the same lock — otherwise the producer could add one more between the consumer's "is it empty?" and "is it done?" questions.

Source

main.sun
using std;
using std.thread;
using std.time;
 
/**
 * Take the front item, if there is one. The lock is held for exactly as long
 * as this function runs: the guard is released when `g` goes out of scope at
 * the end of it, so the caller can wait without stalling the producer.
 */
function take_one(q: const ref Shared<Queue<i64>>) Option<i64> {
  var g = q.lock();
  return g.get().pop();
}
 
// The two thread bodies, as lambdas at global scope. Each takes the handle it
// works through as a parameter rather than capturing one: spawn moves its
// arguments into the thread, so the handle is the thread's from the moment it
// starts, and is released when the thread ends. A global lambda captures
// nothing at all — globals are read directly — so the same one can be spawned
// as often as you like.
 
// Push ten squares onto the queue.
var produce = (q: Shared<Queue<i64>>) => i32 {
  var i: i64 = 1;
  while (i <= 10) {
    // The lock is held just long enough to push: the guard is released at
    // the end of each iteration.
    var g = q.lock();
    g.get().push(i * i);
    i = i + 1;
  }
  return 0;
};
 
// The consumer knows how many items to expect, so it stops after ten rather
// than needing the producer to tell it when to stop.
var consume = (q: Shared<Queue<i64>>) => i32 {
  var received: i64 = 0;
  var total: i64 = 0;
 
  while (received < 10) {
    match (take_one(q)) {
      Option.Some(item) => {
        received = received + 1;
        total = total + item;
      },
      // Nothing queued yet. Step aside rather than spin on the lock —
      // take_one released it before returning.
      Option.None => {
        sleep(create_duration_micros(100));
      }
    };
  }
 
  return _convert<i32>(total);
};
 
/** Runs the example that coordinates producer and consumer threads. */
function main() i32 {
  var alloc = make_heap_allocator();
 
  // One queue, two owners. Sun never copies a class implicitly, so each
  // thread needs its own handle, made with an explicit clone().
  var queue = Shared<Queue<i64>>(alloc, Queue<i64>(alloc));
 
  var producer = spawn(produce, queue.clone());
  var consumer = spawn(consume, queue.clone());
 
  producer.join();
  var total = consumer.join();
 
  // Both threads have finished, so the last handle can read without a race.
  var g = queue.lock();
  println(`items left: ${g.get().size()}`);
  println(`total: ${total}`);
 
  return 0;
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Build and run

./build.sh
./main
# items left: 0
# total: 385