Varyk 0.5.0: packages
Varyk 0.5.0 is on crates.io. It is milestone 5b2 of the roadmap, the third part of the batteries for services. Milestones 5a and 5b1 gave Varyk, a language for backend services that compiles to Rust, the data a service works with and the way it waits: JSON, configuration, logging, tests, and tasks. Milestone 5b2 adds the way Varyk code is shared: a Varyk package uses another Varyk package by name, with no Rust file in between, and every package is built from the Varyk its author wrote. It comes before HTTP and the database because they will be packages too.
What is in it
A package is named by its key. Until now, Varyk code reached any other package, a Varyk library included, through a .rs file of its own, a facade. Now a Varyk package listed in [dependencies] is named in Varyk code like a module, by its key, with each - read as _. With units = { path = "../units" } in Cargo.toml:
use units::length::Meters;
fn total(a: Meters, b: Meters) -> Meters {
units::length::add(a, b)
}
There is no new syntax. You choose the key, so sql = { package = "varyk-sql", version = "0.5" } would be sql:: in your code, and two packages cannot share a name. A local module of the same name wins over a package. A dependency is a Varyk package when its folder has src/lib.vr, whether it comes from a path, a registry, or git. A Rust crate is still never named from Varyk code: it is called from a .rs facade, as before, and naming one is V0110.
Packages that use packages. A package may use packages, to any depth: in the new examples, trip uses route, and route uses units. Each package sees only the packages its own Cargo.toml lists. If route gives you a Meters of units and your package lists route but not units, holding that value is V0115, and the help is the line to add. When cargo picks two versions of one package, they are two packages, and V0115 names both.
What a package gives you. Everything it marks pub that its users can reach: functions, structs with their pub fields, enums, methods, and the pub items of its .rs modules. They behave like your own: parameters borrow, mut parameters change the caller's value, an async function is awaited or started, and a match on its enum is checked for every case. A library cannot hand out a type its users could not name, so a pub function that returns a type of a private module is V0105 in the library itself. Two things stay inside a package: JSON and configuration conversions, so a json or env call on another package's type is V0210 (convert it in its own package), and its #[test] functions. And when any package in a build writes log lines, the program sets up logging, so they are not lost.
What runs when you build. Varyk is for services that face the network, so this release makes one promise about what runs on your machine. When varyk build, varyk run, or varyk test builds your program, the Rust of every Varyk package in it is exactly that package's own .rs files plus what your compiler generates from its .vr files. Nothing else in the package is compiled or run: not its build.rs, and not generated Rust its publisher shipped. varyk asks cargo which packages the build uses, checks every Varyk package from its .vr sources, and builds each as a crate of its own under target/varyk/packages/. What runs is what a reader can review. The package's own .rs files, the Rust crates it depends on, and their build scripts are Rust, visible as files and lines of Cargo.toml, and rustc's to judge, as for your own code.
The promise covers build, run, and test, and the reference names the one command it does not: varyk publish. cargo publish verifies a crate by building it as a plain cargo user would, so publishing compiles a Varyk dependency's shipped Rust, and any build.rs it was published with, on the publisher's machine. --no-verify after -- skips that.
What is refused. Cargo would build a Varyk package on its own, outside that promise, if it were reached any other way, so V0401 refuses a Varyk package under [dev-dependencies], one marked optional that a feature turns on, and one reached through a Rust crate, even when it is also used the supported way. It also refuses a Varyk package with the same name and version as another Varyk package of the build, or as a package that comes from a path. Naming a dependency marked optional in Varyk code is V0401 too, because cargo may leave it out of the build.
Varyk builds Varyk. A package's Cargo.toml now names its .vr root as cargo's target:
[[bin]]
name = "trip"
path = "src/main.vr"
or [lib] with path = "src/lib.vr" for a library. That is what lets cargo's own tools, such as cargo add, cargo update, and cargo tree, read the package. varyk init writes three files, Cargo.toml, .gitignore, and src/main.vr, and no build.rs and no stub, and varyk emit, which the build script ran, is gone. A source package is built with varyk build, not with plain cargo build, since its root is Varyk, not Rust. A Varyk crate published with varyk publish still carries its generated Rust, so a Rust project uses it with plain cargo, as any crate.
New diagnostic codes. V0115 is a value of a package your package does not list, or of another version of one it does. V0405 is cargo failing to work out which packages the build uses, for example a path dependency whose folder is missing, with cargo's own message in the note. V0406 is a Cargo.toml that does not name its .vr root, with the lines to add. The language reference has the new section, and the error codes the full table.
Two new example packages show it, and the examples page has them with their output: route, a library whose legs have a length in Meters of the package units, and trip, a program that uses both. This is trip's Cargo.toml:
[package]
name = "trip"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "trip"
path = "src/main.vr"
[dependencies]
route = { path = "../route" }
units = { path = "../units" }
varyk-std = "0.5.0"
and its src/main.vr:
async fn main() {
let legs = vec![
route::leg("Home", "Mill", 800),
route::leg("Mill", "Lake", 900),
];
let sum = units::length::add(route::total(legs), units::length::Meters { value: 0 });
println!("total {} meters", sum.value);
let best = route::longest(legs);
println!("longest {} to {}, {} meters", best.from, best.to, best.length.value);
for text in vec!["{\"name\": \"Lake\", \"minutes\": 12}", "Lake at noon"] {
match route::parse_stop(text) {
Ok(stop) => println!("stop {}, {} minutes", stop.name, stop.minutes),
Err(_) => println!("no stop"),
}
}
let minutes = route::walking_minutes(sum.value).await;
println!("about {} minutes on foot", minutes);
}
It prints:
total 1700 meters
longest Mill to Lake, 900 meters
stop Lake, 12 minutes
no stop
about 21 minutes on foot
route::longest gives back one of the legs it was given, not a copy, as a function of your own would, and route::walking_minutes is an async function awaited across the package line. trip writes no log line itself, but route warns on stderr when it cannot read a stop, so the program sets up logging for it.
The breaking changes
A package's Cargo.toml must name its .vr root, [[bin]] with path = "src/main.vr" or [lib] with path = "src/lib.vr"; without it, varyk check reports V0406, and its help gives the lines. varyk emit is removed. varyk init no longer writes build.rs or a stub, and a source package is built with varyk build, not plain cargo build. A build.rs or src/main.rs left by an earlier varyk init is never read, so you can delete them. build in [package] may only be left out or false.
The three crates, varyk, varyk-syntax, and varyk-std, are released together as 0.5.0. After cargo install varyk, run varyk check in a package, and it says what to change.
Try it
You need a stable Rust toolchain installed through rustup.
cargo install varyk
varyk init hello
cd hello
varyk run
The getting started page goes from there, and the language reference describes everything the compiler accepts.
What is next
Milestone 5b2 also redraws the rest of milestone 5. varyk-std stays the small runtime every program has: Error, tasks, json, env, and log. Anything heavy, the HTTP server and client, SQL, MongoDB, Redis, is a package the writer adds, so a program that prints a line never compiles a web server or a database driver. Official packages are named varyk-*, such as varyk-sql and varyk-http, and packages from the community *-varyk. With packages in place, a battery is written once as a package, by this project or by anyone, and needs no work in the compiler.
Milestone 5b3 is facades and varyk-sql: what a .rs facade can say, such as a function that takes or returns any Varyk data type, pub use, and varyk add shorthands for official packages; then varyk-sql on sqlx, for SQLite, Postgres, and MySQL, where the query text is written in the program, so a query built from input is a compile error. Milestone 5b4 is varyk-http and the golden path: an HTTP server with an explicit route table, an HTTP client, and the bar for all of milestone 5, a users API on a database that is varyk init, varyk add http sql, one file, and varyk run away. The roadmap has every item, and nothing there is a date.
Varyk is experimental and pre-1.0. Try splitting a small service into packages and tell us where it hurts: the issues are the place.