Libraries

A library is a .loft file you can share across projects. Place it in a lib/ directory, import it with use name; at the top of your file, and then refer to its types and functions using the name:: prefix. All types and functions in a library are accessible — there is no way to mark something as internal or hidden. use statements must come before any fn or struct definition in your file. Putting a use after a definition is a syntax error.

use testlib;

Constants and Enums

Library constants and enum variants are accessed with the libname:: prefix. You can compare, order, and convert enum values exactly as you would with enums defined in the same file.

Struct Construction and Field Access

Construct a library struct with its full namespace prefix. Omitting the prefix is a parse error. Once you have a value, field access uses the plain dot notation with no prefix needed.

Calling Library Methods

Methods defined in the library are called on the value directly — no prefix on the call site. Free functions (not methods) do need the libname:: prefix.

Extending a Library Type

You can add new methods to a library type from your own file. Write the self parameter type with the :: separator and the compiler treats the function as a method on that type.

fn shifted(self: testlib::Point, dx: float, dy: float) -> testlib::Point {
  testlib::Point {x: self.x + dx, y: self.y + dy }
}
fn main() {

Constants from a library require the library prefix.

  assert(testlib::MAX_SIZE == 100, "library constant MAX_SIZE");
  assert(testlib::MIN_SIZE == 1, "library constant MIN_SIZE");

Library enum variants: comparison and ordering work as normal.

  s = testlib::Ok;
  assert(s == testlib::Ok, "enum equality");
  assert(s < testlib::Error, "enum ordering");
  assert("{s}" == "Ok", "enum to text: {s}");
  assert("Warning" as testlib::Status == testlib::Warning, "text to enum");

Library enum values can be passed to library free functions.

  assert(testlib::status_text(testlib::Error) == "err", "free fn with enum arg");

Structs are constructed with namespace prefix and accessed by field.

  p = testlib::Point {x: 3.0, y: 4.0 };
  assert(p.x == 3.0, "field access: x={p.x}");
  assert(p.y == 4.0, "field access: y={p.y}");

Methods defined in the library are called without a prefix.

  dist = p.distance();
  assert(round(dist) == 5.0, "method call: distance={dist}");

User-defined method on a library type (self: testlib::Point) works.

  assert(p.shifted(1.0, 2.0).x == 4.0, "shifted x");
  assert(p.shifted(0.0, 2.0).y == 6.0, "shifted y");

Scalar fields can be mutated directly.

  b = testlib::Bag {label: "test", count: 0 };
  b.label = "updated";
  assert(b.label == "updated", "direct scalar field mutation");

Methods that mutate scalar fields work.

  b.bump();
  assert(b.count == 1, "method scalar mutation: count={b.count}");
  b.bump();
  assert(b.count == 2, "method scalar mutation: count={b.count}");

Free functions: called with the library prefix.

  assert(testlib::add(3, 4) == 7, "free function add");
  assert(testlib::add(0, -5) == -5, "free function negative");

Library types work as function parameter types when written with their full namespace prefix in the function signature. A struct with a vector field can be initialised via a struct literal, including elements that are themselves library structs.

  bag2 = testlib::Bag {label: "full", count: 3, items: [
  testlib::Point {x: 1.0, y: 0.0 },
  testlib::Point {x: 0.0, y: 1.0 }
  ] };
  assert(len(bag2.items) == 2, "initialized vector field: {len(bag2.items)}");
  assert(bag2.items[0].x == 1.0, "vector field element access");

Appending a struct variable with += [var] and a whole vector with += vec both work.

  extra = testlib::Point {x: 7.0, y: 8.0 };
  bag2.items +=[extra];
  assert(len(bag2.items) == 3, "append via var: len={len(bag2.items)}");
  assert(bag2.items[2].x == 7.0, "append via var: x={bag2.items[2].x}");
  more_pts =[testlib::Point {x: 9.0, y: 10.0 }, testlib::Point {x: 11.0, y: 12.0 }];
  bag2.items += more_pts;
  assert(len(bag2.items) == 5, "append vector: len={len(bag2.items)}");
  assert(bag2.items[3].x == 9.0, "append vector[3].x={bag2.items[3].x}");

Importing the same library twice is silently ignored. A library can itself import other libraries using use, so dependency chains work.

Package Layout and loft.toml

A library can be distributed as a directory package instead of a single flat file. The packaged directory layout is:

mylib/
  loft.toml           optional manifest
  src/
    mylib.loft        library source (default entry)

When the interpreter searches a lib directory and finds <dir>/mylib/ it looks for <dir>/mylib/src/mylib.loft automatically.

The optional loft.toml manifest supports two settings:

[package]
loft = ">=1.0"        minimum interpreter version required
[library]
entry = "src/mylib.loft"   override the default entry path

If the interpreter version is below the stated minimum, loading the library produces a fatal compile error describing the version mismatch. If no manifest is present, the default entry src/<name>.loft is used.

Wildcard and Selective Imports

By default, library names require the libname:: prefix. You can import names directly into your namespace with use lib::* (wildcard) or use lib::Name, Other (selective). Only pub-marked definitions in the library are imported this way. Non-pub definitions remain accessible via the lib::name prefix.

Limitations

`use` must appear before all definitions. If you write a function first and then a use, the compiler reports a syntax error.

`pub` controls import visibility. Only pub-marked definitions are available via wildcard (use lib::*) or selective import. Non-pub definitions are still accessible with the lib::name prefix.

No native extension loading. Libraries are pure .loft files. Native Rust extensions (loft.toml with native = "...") are planned for a future release.

}