Chapter 08 8 min read

Modules and packages

Up until now every example has been one file. Real codebases grow past that fast. This chapter is everything you need to organize Glide code in any size from "small CLI" to "service with five libraries."

Modules: one file, one namespace

In Glide, a file is a module. The module's name comes from the path under src/ with the .glide stripped:

output
hello/
  glide.glide
  src/
    main.glide          ← module: main
    storage/
      sqlite.glide      ← module: storage::sqlite
      memory.glide      ← module: storage::memory

To use something defined in another module, import it:

import storage::sqlite::*;     // bring in everything pub from that file

import storage::memory::*;

Read the path with :: as the separator. The ::* at the end means "bring all public items into scope so I can use them by their short name."

Public vs private

By default, anything defined in a module is private — only visible inside that file. To export, prefix with pub:

storage/sqlite.glide
pub struct Db {
    handle: *void,
}

pub fn open(path: string) -> !Db {
    // ...

}

fn _internal_helper() -> i32 {
    // no pub: this stays private to this file

    return 0;
}

From main.glide:

import storage::sqlite::*;

fn main() -> i32 {
    let r: !Db = open("data.db");      // works — open is pub

    return 0;
}

Different ways to import

// Import everything pub from a module:

import storage::sqlite::*;

// Import the module under its short name, call things qualified:

import storage::sqlite;        // then: sqlite::open("data.db")


// Import just one thing by name:

import storage::sqlite::{open};

The * form is the most common. The qualified-name form is nice for resolving collisions when two modules define the same name.

The glide.glide manifest

Every project has a manifest at its root. It's not a config file — it's a Glide value:

glide.glide
let manifest: Package = Package {
    name:        "myapp",
    version:     "0.1.0",
    description: "Example application",
    author:      "Your Name",
    license:     "MIT",
    repository:  "https://github.com/you/myapp",
    bin:         "src/main.glide",   // entry point

    deps: vec_of(
        Dep::git("discord_lib", "https://github.com/you/discord-lib", "v0.1.0"),
        Dep::path("local_lib", "../local-lib"),
    ),
};

The CLI reads the literal fields. The interesting ones:

  • `name` — the package name. Has to match the directory name and the lib entry filename (more on that below).
  • `version` — bumped manually on release; consumers pin to a specific revision rather than this version.
  • `bin` — the source file with fn main(). For a library package, set this to "".
  • `deps` — a vec_of(...) list of Dep values. Two kinds: Dep::git(name, url, rev) and Dep::path(name, local_path).

Adding a dependency

You can edit glide.glide by hand, or use the CLI:

shell
glide add discord_lib github.com/you/discord-lib v0.1.0

That appends the dep to your manifest. Then download it:

shell
glide fetch

glide fetch pulls each dep into a content-addressed cache (~/.glide/cache/<sha256>) and creates a symlink at glide_modules/<dep-name> pointing to it. You commit your glide.glide and glide.lock to git; you do not commit glide_modules/ (the install script regenerates it).

Use the dependency by importing its package name:

import discord_lib::*;
// or, if its symbols live in submodules:

import discord_lib::gateway::*;
import discord_lib::rest::*;

Publishing a library

Want others to depend on your code? Three steps:

  1. Structure: create the directory with this layout (use glide new mylib --lib):
output
mylib/
  glide.glide        # name: "mylib", bin: ""
  src/
    mylib.glide      # entry point — same name as the package
    # ... more files here, importable as mylib::other_file

The entry-file's name has to match the package name. That's how import mylib; finds the right file.

  1. Publish: push to GitHub and tag a version:
shell
git tag v0.1.0
git push origin v0.1.0
  1. Consume: anyone can now add your library:
shell
glide add mylib github.com/you/mylib v0.1.0
glide fetch

Building for production

When you're ready to ship, glide build --release makes the optimized binary:

shell
glide build --release
# → ./build/myapp (or myapp.exe on Windows)

That binary is self-contained: it doesn't need Glide installed on the target machine. Copy it to your server, run it, done.

For cross-compiling to another OS without leaving your machine:

shell
glide build --release --target=x86_64-linux-gnu       # Linux
glide build --release --target=aarch64-apple-darwin   # macOS ARM
glide build --release --target=x86_64-windows-gnu     # Windows

The bundled zig cc handles the cross-build — no separate toolchain to install.

Reading the dep tree

glide.lock tracks the exact revision (commit SHA + content hash) of each dependency you've fetched, so a fresh clone resolves to the same code. Commit it to git just like package-lock.json or Cargo.lock.

Inspect what's actually pulled in:

shell
ls glide_modules/         # top-level deps
ls glide_modules/<dep>    # source of one dep

glide_modules/ is a flat list of symlinks into the content-addressed cache. Two projects depending on the same revision of the same library share one underlying copy on disk.

A realistic project layout

What an HTTP service looks like by the time it's a few thousand lines:

output
myservice/
  glide.glide                        # name, version, deps
  glide.lock
  src/
    main.glide                       # fn main() — wires everything together
    config.glide                     # env parsing, validation
    handlers/
      health.glide
      users.glide
      posts.glide
    storage/
      pool.glide                     # connection pool
      queries.glide                  # SQL
    middleware/
      auth.glide
      logging.glide
  book/                              # this site, perhaps
  static/
  tests/
  glide_modules/                     # not committed; populated by glide fetch
    sqlite/
    redis/
    auth_jwt/
  build/                             # not committed; output of glide build

Inside main.glide you'd see:

import handlers::health::*;
import handlers::users::*;
import handlers::posts::*;
import middleware::auth::*;
import middleware::logging::*;
import sqlite::*;

fn main() -> i32 {
    // ... wire up routes, start server

    return 0;
}

Recap

Where to next

That's the core language and its tooling. The remaining chapters put it to work: the next one — A tour of the standard library — surveys the batteries Glide ships with, the APIs you'll actually call day to day.

After that: building an HTTP server, testing and benchmarks, and FFI and escape hatches.