Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Modules and packages

A rut file is a module: one namespace, one visibility scope. A directory with a rut.toml is a package — the unit you depend on and share. This chapter walks up those two levels. The reference pages are modules and visibility, project structure and rut.toml, and dependency kinds.

What lives at module scope

Module scope contains declarations only: use, let, fn, struct, class, enum, trait, impl. Every statement lives inside a function — and loading a module executes nothing. There is no load-time side-effect ordering to reason about; the host loads your module and calls its entry point (conventionally pub fn main).

Module-level let initializers must be load-time literals — 42, "app", true. Arithmetic, record literals, and calls to user functions are not accepted in this build (there is no mutable module state; programs build their state in main).

use pouch::{ Vec };
use ink::{ Logger };

struct Point { x: f32; y: f32 }

let version = 1;                       // fine: a literal
let app_name = "app";                  // any literal works

fn main_body() { /* statements live here */ }

pub fn main() {
    let log = Logger.new("app");
    let origin = Point { x: 0, y: 0 }; // record literals live in function bodies
    log.info(f"{app_name} v{version} origin.x={origin.x}");
}
app v1 origin.x=0

Visibility

Every declaration has a visibility, and unannotated means module-private — the safe default. Nothing leaks unless it says pub:

FormMeaning
pub fn ..public — importable by any module, other packages included
pub(mod) fn ..visible everywhere in this package’s module tree
pub(super) fn ..visible to the parent module only
fn ..module-private (pub(self)) — the default

The same forms apply to types, module lets, and class members. Structs are the exception: a struct is an open record — all members public, always, and its impl methods take no visibility annotation (they are as public as the type).

use — naming other modules

use imports names with Rust-like paths:

use ink::{ Logger };
use pouch::{ Vec };
use json::{ decodeJsonBytes, JsonDeserialize };

Builtin names — the primitives, str/bytes members, panic, assert, Vec-free array grammar, opaque — are ambient: no use needed. Package names from your manifest are; an unused name in a use is a lint, not an error.

Within one module, everything is visible — including declarations later in the file. Order never matters.

Packages: rut.toml

A package is a directory with a manifest. The small but complete case — one library package and one app:

greet/
├── pkg/
│   ├── rut.toml
│   └── greet.rut
└── app/
    ├── rut.toml
    └── main.rut
# greet/pkg/rut.toml
name = "greet"
entry.lib = "./greet.rut"

[deps]
pouch = { path = "../../rut/pouch" }   # the toolchain tree's pouch package
// greet/pkg/greet.rut
use pouch::{ Vec };

pub struct Greeting {
    to: str;
    lines: Vec<str>;
}

impl Greeting {
    pub fn new(to: str) -> Self {
        return Self { to: to, lines: Vec.new() };
    }

    pub fn add(mut self, line: str) {
        self.lines.push(line);
    }

    pub fn render(self) -> str {
        let mut out = f"dear {self.to},";
        for (let line of self.lines) {
            out = f"{out} {line}";
        }
        return out;
    }
}

fn shout(msg: str) -> str {   // module-private: no `pub`, never importable
    return f"{msg}!";
}

The app names its dependencies in [deps], by path — each package pulls its own dependencies along (ink brings the host surface rt; you never spell it):

# greet/app/rut.toml
name = "app"
entry.lib = "./main.rut"

[deps]
greet = { path = "../pkg" }
ink = { path = "../../rut/ink" }       # the logger package
// greet/app/main.rut
use greet::{ Greeting };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("app");
    let mut g = Greeting.new("rut");
    g.add("hello");
    g.add("from a package");
    log.info(g.render());
}

Run the app from its directory:

rut run .
dear rut, hello from a package

Resolution walks [deps] recursively (a dep’s own [deps] mount with it), with a cycle guard and first-mount-wins. Single loose files get a convenience: the rut CLI mounts the toolchain’s tree packages when your file says use ink:: or use pouch:: — see the rut CLI.

One package, several files

A package’s body can be split across files. The manifest splices them, base first, in listed order — into one module: one namespace, one visibility scope. A name private to one file is visible to every other file of the same package:

name = "app"
entry.lib = "./biz.rut"
entry.libs = ["./domain.rut", "./world.rut", "./app.rut"]

This is assembly, not an include form — cross-package references still go through use paths.

Dependency kinds

Beyond [deps], a manifest can declare two other relations:

  • [peer-deps] — a package this one integrates with but never pulls: the consumer supplies it, or (with optional = true) the integration mounts only if the peer is already in the program’s closure. The standard library’s json package uses this to attach its Vec/map serialization impls only for programs that carry the container packages.
  • [dev-deps] — mounted only when building/testing the package itself, never for a consumer.
[peer-deps]
pouch = { path = "../pouch", optional = true, lib = "./group-pouch.rut" }

[dev-deps]
pouch = { path = "../pouch" }

entry fn — the host-facing surface

pub publishes a name to other rut modules. entry publishes a function to the embedding host, with its signature checked against the boundary’s crossing rules at compile time:

entry fn hex_enc(data: bytes) -> str { .. }

An embedded application drives these entries; pub fn main is the conventional entry the rut run CLI calls. See the host boundary.

Tooling

rut run <file.rut | dir | mod.rutbundle> [--fuel N]  # compile + run
rut fmt <file.rut | dir> [--check]                   # format in place
rut pack <dir> [-o out.rutbundle]                    # a self-contained bundle
rut dump <file.rut>                                  # dump module info

Put it together

The greet project above is complete as shown — two directories, two manifests, two sources. Copy the trees into files and rut run . from greet/app to see:

dear rut, hello from a package

Next: async: tasks, workers, and channels.