Shared tape libraries
A library written in tape can be built once and loaded by the programs that use it, instead of being compiled into each of them. Unlike a C-style library (@export functions reached through @link and extern fn, see Linking & FFI), it keeps its whole tape interface: functions and methods, structs, enums, tagged unions, strings, and components with their props, events and views.
Declaring a library
The library’s root module names it. The name ends in its version:
// toolkit.tape
@library("toolkit3");
import look from "./look.tape";
pub fn spacing() -> i64 { return 8; }
pub component Button {
prop label: string = "";
event on_click();
// ...
}The root is t1 (or t0) code, which t2 programs import as they import any t1 module; the modules it imports may be t2. The library owns its root and every module the root imports, directly or not, except another library’s modules and the bridges to tape’s own libraries (tapert100, uwm100, image100).
Building it and using it
tape build toolkit.tape --target uusi --dll -o toolkit3 # the library: toolkit3.sto
tape build app.tape --target uusi --libraries shared -o app # a program that uses itThe program does not change. It imports the library’s modules as usual and uses them as usual:
import tk from "toolkit";
component App {
view {
tk.Button { label: "Save"; on_click: save; }
}
fn save() { /* ... */ }
}It is compiled against the library’s source (its types, components and generators are read from it), but every function, method and component of the library it uses is loaded from /lib/toolkit3.sto (on Leivos, /libraries/toolkit3) when first called. Without --libraries shared (the default) the library is compiled into the program, which is what you want where the library is not installed.
- One copy. A module the library owns is reached through the library even when the program imports it directly, so there is one copy of its variables in the process.
- Variables through functions. A program reaches a library’s variables through the library’s functions: reading or writing one directly is a compile error with
--libraries shared. - Components are made by the library, so it can give them new props later. Generic code a program generates (
collections.gen_vec(tk.Item)) is the program’s own.
Changing a library without breaking programs
A newer build under the same name may:
- add functions, methods, components, structs, enums, tagged unions and variables;
- append props to a component, with defaults, and change its vars and state;
- add a field to an
@opaquestruct; - change any function’s body.
Anything else needs a new name (toolkit4, installed beside toolkit3): removing or renaming what a program could use, changing a signature, the fields of a struct that is not @opaque, an enum’s or tagged union’s variants, a const’s value, a prop’s or event’s type, or a component’s events or slots.
Building a library also writes toolkit3.tapeapi beside it: its public interface, one declaration a line. Keep the one you released and check a new build against it before installing it under the same name:
tape lib check released/toolkit3.tapeapi build/toolkit3.tapeapiIt prints compatible: ..., or each breaking change and exits with 1.
Programs and libraries also record the tape ABI version of the compiler that built them (tape --version prints it), which changes only when the way compiled code meets compiled code changes; a program refuses a library built for another one, with a message naming it.
Last modified: