W# 0.1.1

Modules

@import binds a module to a name, pub says what another module may see.

// The standard library, and the module system it sits behind.
//
// `@import` binds a module to a name. Two files may each declare a `helper`
// without colliding, and the 27 HTTP status types no longer occupy the global
// namespace -- which is what the module system was for.

const str = @import("std/str");
const array = @import("std/array");
const math = @import("std/math");
const io = @import("std/io");
const http = @import("std/http");

fn render(s: http.Status) str { return "500 server error"; }
fn render(s: http.Status4xx) str { return "400 bad request"; }
fn render(s: http.NotFound404) str { return "404 not found"; }

// Statically a `Status`, so which overload wins is read from the type id in
// the object's header.
fn serve(s: http.Status) void { print(render(s)); }

fn main() i64 {
    // Strings. `==` compares contents, so a string built at run time equals a
    // literal.
    const greeting = str.concat("hello, ", "world");
    print(greeting);
    print_int(str.len(greeting));
    print_bool(greeting == "hello, world");
    print(str.substr(greeting, 7, str.len(greeting)));

    // Splitting produces an array of strings, which `join` puts back.
    const fields = str.split("id,name,email", ",");
    print_int(array.len(fields));
    print(str.join(fields, " | "));

    // Arithmetic that is not an operator. `abs` is an overload set; `min` is
    // one function over the abstract type `Number`.
    print_int(math.abs(-7));
    print_float(math.sqrt(2.0));
    print_int(math.min(3, 7));
    print_int(math.ipow(2, 10));

    // I/O, where the library first has to fail: a fallible builtin returns a
    // `!T`, caught like any other error.
    print(io.read_file("/definitely/not/a/file") catch "could not read it");

    serve(http.NotFound404);
    serve(http.Forbidden403);
    serve(http.ServerError500);
    return 0;
}
wsharp run examples/library.ws
hello, world
12
true
world
3
id | name | email
7
1.4142135623730951
3
1024
could not read it
404 not found
400 bad request
500 server error

Importing

@import binds a module to a name, and everything in it is reached through that name:

const str  = @import("std/str");      // a library module
const util = @import("./helper.ws");  // a file next to this one
const json = @import("acme/json");    // a package this project depends on

Library paths and package paths are extensionless. A relative path names a file and keeps its extension.

The name is yours to choose. const text = @import("std/str"); is fine, and the example programs use whichever name reads best at the call site.

Two files may each declare a helper without colliding, and the 27 HTTP status types no longer occupy the global namespace, which is what the module system was for.

Visibility

Everything in a module is private to it unless it says pub:

pub const State = struct { total: i64, label: str };
pub fn add(s: State, n: i64) i64 { ... }
fn internal() i64 { ... }            // not reachable from outside

Only a qualified lookup checks visibility. An unqualified name can only mean this module’s own or the prelude’s, and both are always visible, so pub costs nothing to resolve.

Re-export

A const may be a second name for something another module declares, which is what lets a package of several files present one of them:

const inner = @import("./inside.ws");

pub const Pair  = inner.Pair;      // a type
pub const twice = inner.twice;     // a function, or a whole overload set

The alias and the original are the same type and the same function set rather than copies, so a value made through one is usable through the other, and an overload set renamed once still dispatches on every member.

What a program pays for

A library module is read only if something imports it. A program that mentions nothing pays for nothing: wsharp check on a ten-line file takes about five milliseconds however far the library grows.

The HTTP status types go further. They are materialised on first mention, so a program that never names one carries none of them.

Modules are also workers

There is no separate declaration form for a service. A module with an init that makes its state, and functions taking that state as their first parameter, is something @spawn can start:

// A service: a module with an `init` that makes its state, and functions
// taking that state as their first parameter. No new declaration form -- W#
// has no mutable globals, so a worker's state had to be an explicit value
// passed in and out, and once it is, the functions that take it are exactly
// the things the worker can be asked to do.
pub const State = struct { total: i64, label: str };

pub fn init(start: i64, label: str) State {
    return State{ .total = start, .label = label };
}

pub fn add(s: State, n: i64) i64 {
    s.total = s.total + n;
    return s.total;
}

pub fn total(s: State) i64 { return s.total; }

pub fn describe(s: State) str { return s.label; }

// Not a method: it does not take the state, so no caller can reach it through
// a handle.
pub fn helper() i64 { return 0; }

helper does not take the state, so no caller can reach it through a handle. That is the next page.

Last changed 8 September 2026. Improve this page

On this page