Simple services. Rust underneath.
Build backend services simply. Ship Rust binaries.
Varyk is a small language for APIs, workers, and microservices. No garbage collector, no lifetime annotations, no borrow ceremony, and Rust and crates.io underneath. You write Go-like application code and ship one native binary, checked by the Rust compiler.
Install with Cargocargo install varyk --locked
users.vr
struct User {
id: i64,
name: string,
created_at: Time,
}
struct NewUser {
name: string,
}
struct State {
db: sql::Pool,
}
async fn get_user(id: i64, state: Shared<State>) -> Result<Option<User>, Error> {
state.db.first("select id, name, created_at from users where id = $1", id).await
}
async fn create_user(user: NewUser, state: Shared<State>) -> Result<http::Response, Error> {
if user.name.is_empty() {
return Err(http::bad_request("a user needs a name"));
}
let created: User = state.db.one("insert into users (name, created_at) values ($1, $2) returning id, name, created_at", user.name, Time::now()).await?;
let mut r = http::Response::json(created);
r.set_status(201);
Ok(r)
}
async fn open_db() -> Result<sql::Pool, Error> {
let db = sql::connect("sqlite://users.db?mode=rwc").await?;
db.run("create table if not exists users (id integer primary key, name text not null, created_at text not null)").await?;
Ok(db)
}
async fn main() {
let port: u16 = 3000;
let db = match open_db().await {
Ok(db) => db,
Err(e) => {
log::error("cannot open the database: {}", e);
return;
}
};
let mut app = http::App::new(Shared::new(State { db: db }));
app.get("/users/{id}", get_user);
app.post("/users", create_user);
if let Err(e) = app.serve(port).await {
log::error("cannot serve: {}", e);
}
}created_at: Time of milestone 5c's design; it runs with the varyk-http package, and varyk check checks each route against its handler. http and sql are the keys varyk add http sql gives the varyk-http and varyk-sql packages, named as any Varyk package is since milestone 5b2; varyk-sql arrived with milestone 5b3, and varyk-http with milestone 5b4. Error and log are here since milestone 5a, async and Shared since milestone 5b1, and Time since milestone 5c. See the roadmap.What a service needs. What Rust guarantees.
Rust underneath gives you all of this today. The batteries that make a service, what a Go or TypeScript team expects to find built in, are listed below them: JSON, configuration, logging, and tests arrived in milestone 5a, async in milestone 5b1, packages that use packages in milestone 5b2, facades for packages in milestone 5b3, routes checked against their handlers in milestone 5b4, and times, ids, and bytes in milestone 5c; databases and HTTP come as packages, varyk-sql since milestone 5b3 and varyk-http since milestone 5b4, and MongoDB as varyk-mongo, after milestone 5.
- One file to deploy. A whole program is one executable, under half a megabyte for the
todoexample built on an Apple Silicon Mac, with no language runtime to install. - Starts instantly, stays small. The
todoexample starts in a few milliseconds and uses about a megabyte and a half of memory on an Apple Silicon Mac. There is no garbage collector, so no pauses and no memory ceiling to tune. - Bugs caught before the program runs. No null: a value that can be missing is an
Option, and you must handle it. Errors are values, not exceptions: a function that can fail says so in its return type, and there is nothing to catch. Two threads cannot touch the same data unsafely. All of it is rustc checking your program, not Varyk approximating it. - As fast as Rust, because it is Rust. Native code, no interpreter, no just-in-time compiler, nothing cloned behind your back.
- No ownership ceremony. No lifetime annotations, no choosing between
x,&x, and&mut xat a call site, and one string type. The compiler makes those decisions, and rustc checks every one of them. - Readable Rust, yours to keep. The generated Rust is a normal Cargo project,
--emit-rustshows all of it, and there is no lock-in. - The whole Rust ecosystem. A Varyk package is a Cargo package: since milestone 3 you add a crate with
cargo add, orvaryk addsince milestone 5a, and call it from a.rsfile in the package, and the next section shows how. - Drop into Rust when you need it. A
.rsfile beside your.vr, in the same package and the same build. Write the borrow-heavy part in Rust and the rest in Varyk. Use Varyk until you actually need Rust.
The batteries
JSON, configuration, logging, and tests are in the compiler since milestone 5a, async since milestone 5b1, and Time, Uuid, and Bytes since milestone 5c; the examples show them. Databases and HTTP come as packages you add, varyk-sql, varyk-mongo, and varyk-http, so a program that prints a line never compiles a web server or a database driver. Milestone 5b3 gave the compiler what varyk-sql needs, and milestone 5b4 what varyk-http needs; varyk-mongo came after milestone 5. Each package lives in its own repository and is released on its own. The figure at the top of the page is a service with varyk-http and varyk-sql.
- JSON from your structs. Turn your structs into JSON and back with
json::parseandjson::stringify, and rename, default, or skip a field with an attribute. Milestone 5a. - Configuration, logging, tests. A struct filled from the environment and a
.envfile withenv::parse, log lines on stderr as text or JSON, andvaryk test. Milestone 5a. - Async built in.
async fnand.awaiton a built-in runtime; call a function without.awaitto start it as a task, and wait on many withTask::all. Milestone 5b1. - Times, ids, and bytes.
Time, a point in time in UTC written as2026-10-07T12:00:00Z,Uuid, withUuid::new()a time-ordered id, andBytes, base64 in JSON, as built-in types that go through JSON and intovaryk-sql's native columns, so acreated_atis a time and not a string. Milestone 5c. - Databases through one API. Query SQLite, Postgres, or MySQL and get your structs back, in the
varyk-sqlpackage on sqlx; the query text is written in the program, so a query built from input is a compile error. Milestone 5b3. - HTTP server and client. An explicit route table that
varyk checkchecks against each handler, handlers whose return value is the response, errors whose internal message never reaches a client, and calls to other services, in thevaryk-httppackage on axum and reqwest. Milestone 5b4. - MongoDB. Documents from your structs, with times, ids, and bytes as native BSON types, and filters, updates, and pipelines in MongoDB's own syntax with the values beside the text, in the
varyk-mongopackage on the officialmongodbcrate;varyk add mongoadds it. After milestone 5.
Varyk exists so that ordinary backend services can be written simply and shipped as safe Rust. It is for the application: kernels, database engines, and borrow-heavy libraries stay in Rust, in a .rs file beside your Varyk, in the same build. Read why Varyk exists.
Varyk packages are Cargo packages.
A Varyk package is a Cargo.toml and a src/main.vr. varyk init writes one, varyk run builds it, and varyk publish ships it to crates.io as an ordinary Rust crate that needs no Varyk to use. Since milestone 5b2, a Varyk package uses another Varyk package by its key in Cargo.toml, with no facade, and varyk builds every one of them from its .vr sources, never from Rust its publisher shipped. There is nothing to bootstrap: every crate on crates.io was Varyk's ecosystem from the first day, and every Varyk package joins it.
main.vrYour service, in Varyk. It never names a Rust crate, and it never writes a reference.text.rsA Rust file beside it: a facade that wraps what the program needs in plain functions, structs, and enums, which Varyk imports like a module of its own. Since milestone 5b3, a facade can also read its result into whatever type the caller names and take any number of values, and since milestone 5b4 take any value JSON can write, so a package such asvaryk-sqlkeeps its Rust in a facade, and a program that uses it writes no Rust.- crates.io Any crate, added with
varyk addorcargo add. Need Stripe, AWS, or Kafka? Use the Rust crate.
Need something Varyk doesn't provide? Use the Rust crate. Need Rust itself, for the one hot loop or the borrow-heavy library? Write it in the .rs file, in the same build, and call it from Varyk. The matcher example wraps regex-lite this way, and the tools page describes init and publish.
How it works.
Most of Rust's surface is the spelling of decisions the compiler can make on its own. Varyk keeps Rust's ownership model inside the compiler and takes the spelling out of the language. This program runs today; everything marked is what the compiler wrote for you.
borrowing.vr
struct User {
name: string,
}
fn rename(mut user: User) {
user.name = "Bob";
}
fn print_user(user: User) {
println!("{}", user.name);
}
fn main() {
let mut user = User {
name: "Alice",
};
print_user(user);
rename(user);
print_user(user);
}generated Rust
#[allow(warnings, arithmetic_overflow, unconditional_panic)]
#[derive(Clone, PartialEq)]
struct User {
name: String,
}
#[allow(warnings, arithmetic_overflow, unconditional_panic)]
fn rename(user: &mut User) {
user.name = "Bob".to_string();
}
#[allow(warnings, arithmetic_overflow, unconditional_panic)]
fn print_user(user: &User) {
::std::println!("{}", user.name);
}
#[allow(warnings, arithmetic_overflow, unconditional_panic)]
fn main() {
let mut user = User { name: "Alice".to_string() };
print_user(&user);
rename(&mut user);
print_user(&user);
}varyk build generates from borrowing.vr, as the compiler writes src/main.rs. Marked: everything the compiler writes for you. The program prints Alice, then Bob.| Rule | In Rust you write | In Varyk you write |
|---|---|---|
| Functions borrow by default. The signature carries the contract. | fn rename(user: &mut User) | fn rename(mut user: User) |
Call sites never write &. No choosing between x, &x, and &mut x. | rename(&mut user); | rename(user); |
| One string type. The compiler decides the representation. | name: "Alice".to_string() | name: "Alice" |
From your source to the binary:
.vrVaryk source, with.rsfiles beside it in the same build.varyk buildParses, type-checks, and runs borrow analysis. Diagnostics carry codes and fix-its.target/varyk/A Cargo project of readable Rust. It is yours, so there is no lock-in.cargorustc and the full borrow checker. Varyk never usesunsafeto get around it.- A native binary No garbage collector, no reference counting, no runtime beyond Rust's.
Wherever you’re coming from.
Building services in Go, TypeScript, or Python
- Application code as simple as Go: the compiler does the memory bookkeeping that Rust asks you to write by hand.
- Native speed and memory safety with no garbage collector: no pauses, no memory ceiling to tune, and one small binary to deploy with no language runtime to install.
- The bugs Go and TypeScript compile, a missing value, an unhandled error, two threads on one piece of data, rustc rejects.
async/awaitthat looks like JavaScript, with a built-in runtime, since milestone 5b1.- One toolchain and one package registry, inherited from Cargo and crates.io, since milestone 3.
Coming from Rust
- Rust syntax and idioms: immutability by default,
let mut,struct,impl,match,Option,Result, and?. - The same safety model: the generated Rust is checked by rustc, and Varyk never bypasses it.
- Less ceremony for application code.
.rsfiles next to.vrfiles in the same package, built by the samecargo.- Varyk packages are Cargo packages and publish to crates.io as ordinary crates, since milestone 3.
Written with a coding agent
- Agents already write good code; the slow part is reviewing it. Varyk is designed to keep reviews short.
- A small language with one way to do each thing, so agent output looks like the code around it.
- Concrete code, with few abstractions to see through.
- rustc checks memory safety and data races, so a review can focus on what the service does.
- A reference that fits in a prompt, and JSON diagnostics with codes and fix-its, so the agent fixes its own mistakes first.
- One official stack for HTTP, JSON, and databases, the same in every Varyk service (JSON since milestone 5a; databases and HTTP as the packages
varyk-sql, since milestone 5b3, andvaryk-http, since milestone 5b4, and MongoDB asvaryk-mongo, after milestone 5).
Ordered milestones, no dates.
- Milestone 1, complete. Compiler skeleton and the borrow-by-default proof: six example programs, diagnostics with codes and fix-its, and
varyk check,build, andrun. - Milestone 2, complete. Enums,
match,for, methods,Option,Result,Vec,?, andformat!. - Milestone 3, complete. Packages and interop:
Cargo.toml, Cargo dependencies through.rsfacades, nested modules,use, private fields, Rust structs and enums imported from.rsfiles,varyk init, and publishing to crates.io. Released as 0.1.0. - Milestone 4, complete. Closures, iterators, and patterns: chains such as
filterandmap,if letandwhile let, nested, literal, and range patterns,HashMap,as,.clone()and==on structs and enums, and getters that return part of their value with no copy. Released as 0.2.0. - Milestone 5a, complete. Data, configuration, logging, and tests: a built-in
Error,json::parseandjson::stringify,env::parsewith.env,logcalls, the attributes#[rename],#[default],#[skip], and#[test], andvaryk testandvaryk add. Released as 0.3.0. - Milestone 5b1, complete. Async:
async fnand.awaiton a built-in runtime, calls started as tasks,Task::allandTask::all_settled, andSharedfor a value many tasks read. Released as 0.4.0. - Milestone 5b2, complete. Packages: Varyk code uses a Varyk package by its key in
Cargo.toml, to any depth, andvarykbuilds every package from its.vrsources, never running Rust a publisher shipped or a build script. Released as 0.5.0. - Milestone 5b3, complete. Facades for packages: a
.rsfacade function that reads its result into whatever type the caller names, takes any number of values after its other arguments, or takes only text written in the program, pluspub useandvaryk add sql. Released as 0.6.0, with the database packagevaryk-sql, in its own repository, released as 0.1.0. - Milestone 5b4, complete. HTTP: routes and hooks on an app of the
varyk-httppackage, each route checked against its handler byvaryk check, an optional status onError,app.requestfor tests, andvaryk add http sql. Released as 0.7.0, with the HTTP packagevaryk-http, in its own repository, released as 0.1.0: a users API on a database isvaryk init,varyk add http sql, one file, andvaryk runaway. - Milestone 5c, complete. Time, ids, and bytes:
Time,Uuid, andBytesas built-in types, through JSON, database values, and facades, andTimeandUuidthrough configuration and route parameters, so the users API does not send a date as a string. Released as 0.8.0, withvaryk-sql0.3.0, which stores the three as native columns, andvaryk-http0.2.0, which reads times and ids from routes and sends and receives bytes; with them, milestone 5 is met. - Milestone 6, in progress. Tooling and beyond:
varyk fmt, a language server, more of Rust imported from.rsfiles, and a decision on a native backend. The first of it came as 0.9.0:varyk lsp, which shows errors and quick fixes in an editor when a file is opened or saved, and avaryk-stdline that names the oldest varyk a package needs, so a varyk release no longer stops a published package from building. Not scheduled.
After milestone 5 came the next packages, each in its own repository: the MongoDB package varyk-mongo, released as 0.1.0, and varyk-sql 0.4.0, with $1 placeholders on every database. Varyk 0.8.1 added what varyk-mongo needs, and 0.8.2 added varyk add mongo. varyk-redis is next.
The roadmap has every item. None of it has a date, and none of it is a promise of a release.
Install. Write. Run.
Varyk requires a stable Rust toolchain, 1.88 or newer, installed through rustup. Install the compiler, write a package, and run it:
$ cargo install varyk --locked
$ varyk init hello
$ cd hello
$ varyk run
Hello, world!
init wrote Cargo.toml, .gitignore, .dockerignore, and src/main.vr, the program. For colour and errors in your editor, install the Varyk extension in VS Code, or in Zed once Zed lists it (set up your editor). Continue with getting started, which ends with a first service on HTTP and a database, and the language reference. Source and issues are at github.com/Varyk-Lang/varyk.