Skip to content

Imports and packages

Beans has one import keyword. It reaches the standard library by dot path, local packages by module path, and remote libraries straight from a Git host.

import std.io
import std.thread
import shop.util // <root>/util/*.b, used as util.thing
import shop.money.fx // nested: <root>/money/fx/
import github.com/acme/http // cloned on first build
import gitlab.com/tools/csv as csvlib

It pays to keep these apart:

IdeaExampleWhat it is
module pathshopthe beans.pot unit: one dependency, one lock row
import pathshop.moneya package’s globally unique identity
package namemoneywhat the package calls itself in its package clause
import bindingcash in import shop.money as casha name, in one file only

The binding is the name you actually write to reach the package. By default it is the package’s declared name (not the last path segment). import shop.transport_v2 binds transport when that directory declares package transport. Use as to override it.

Reach anything marked pub in the imported package by qualifying it with the binding:

util.some_fn()
let u: util.User = new util.User("jul")
util.color.red

The methods of a pub interface travel with it; an interface is its method set. A plain fn init(...) can be used from any file in the class’s own package. pub fn init(...) is needed only when another package writes new Conn(...); the class itself must be pub too.

An import belongs to the file that wrote it. Two files of one package may give the same alias to different packages, and an import in one file qualifies nothing in its siblings. Two imports with the same local name in one file are an error; separate them with as.

A package’s identity is its whole import path. Two packages may freely share a declared name, and two paths may share a final segment. a/cart and b/cart both call themselves cart; give the imports different local names and both work:

import shop.a.cart as retail
import shop.b.cart as wholesale
let a: retail.Cart = new retail.Cart()
let b: wholesale.Cart = new wholesale.Cart()

They stay separate everywhere: separate types, separate private methods, separate generated symbols.

Packages form a directed graph. A package importing itself, or a cycle through several packages, is refused with the whole chain:

package import cycle:
shop.a imports shop.b at a/a.b:3
shop.b imports shop.c at b/b.b:2
shop.c imports shop.a at c/c.b:4

Files of one package create no edges between each other, so mutually recursive functions in one package are fine. A diamond is acyclic and loads its shared dependency once.

  • std.* resolves to the shipped standard library.
  • <module> or <module>.<...> resolves to a local package under your module root.
  • host/owner/repo[/sub...] (a first segment containing a ., at least three segments) resolves to a Git dependency, cloned and cached on first build.

Without a beans.pot above your file, you are in single-file mode: std.* and Git imports still work, but local packages do not. The manifest, dependency resolution, and the lock file are covered in POT package management.