Introduction

Husk is a modern programming language that brings Rust's elegant syntax and powerful type system to the JavaScript ecosystem. Write code with familiar Rust patterns and compile to clean, readable ES modules that run on Node.js.

Why Husk?

  • Rust-inspired syntax - Structs, enums, pattern matching, and Result types
  • Strong static typing - Catch errors at compile time with algebraic data types
  • JavaScript output - Generate clean ES modules with full npm ecosystem access
  • First-class tooling - LSP support, VS Code extension, formatter, and watch mode

Quick Example

struct Person {
    name: String,
    age: i32,
}

fn greet(person: Person) -> String {
    format!("Hello, {}! You are {} years old.", person.name, person.age)
}

fn main() {
    let alice = Person { name: "Alice".to_string(), age: 30 };
    println!("{}", greet(alice));
}

This compiles to readable JavaScript:

const alice = { name: "Alice", age: 30 };
console.log(`Hello, ${alice.name}! You are ${alice.age} years old.`);

Getting Started

Ready to try Husk? Head to the Installation guide to get started.

Installation

If you have Rust installed, you can install Husk using Cargo:

cargo install husk-lang

Verify Installation

After installation, verify that Husk is working:

husk --version

Editor Setup

For the best development experience, install the VS Code extension or configure your editor with LSP support. See Editor Setup for details.

Hello World

Let's write your first Husk program.

Create a File

Create a file called hello.hk:

fn main() {
    println!("Hello, world!");
}

Compile and Run

Compile the file to JavaScript:

husk build hello.hk

This creates hello.js. Run it with Node.js:

node hello.js

You should see:

Hello, world!

Watch Mode

For rapid development, use watch mode to automatically recompile on changes:

husk watch hello.hk

Next Steps

Now that you have Husk running, learn about the Basic Syntax.

Basic Syntax

Coming soon.

Types

Husk uses static types for source checking and erases most type information in the generated JavaScript.

Primitive Types

Core primitive types are:

  • i32
  • i64
  • f64
  • bool
  • String
  • ()

Integer and float types are checked by Husk. JavaScript output uses native numbers where possible and runtime helpers where the compiler needs special handling.

Structs and Enums

Structs are nominal record types:

struct User {
    id: String,
    name: String,
}

Enums support unit and payload variants:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Enums compile to tagged JavaScript objects and can be checked exhaustively in match expressions.

Generics

Generic types and functions are type-checked but erased at runtime:

fn first<T>(items: [T]) -> Option<T> {
    if items.len() > 0 {
        Some(items[0])
    } else {
        None
    }
}

All instantiations share the same JavaScript implementation.

JavaScript Types

The standard JavaScript interop types are opaque wrappers:

  • JsValue
  • JsArray<T>
  • JsPromise<T>
  • JsFn
  • JsObject

Use these for imported JS values that do not map cleanly to Husk. Prefer wrapping them behind named Husk functions or structs before using them in app logic.

TypeScript Imports

The d.ts importer maps TypeScript declarations into the closest Husk shape it can represent. Simple functions, interfaces, classes, arrays, promises, and literal unions can often become typed declarations. Complex unions, intersections, conditional types, any, and unknown may become JsValue.

This is intentional: imported JavaScript values remain shared and mutable, and Husk does not try to emulate the full TypeScript type system.

Functions

Coming soon.

Structs

Coming soon.

Enums

Coming soon.

Pattern Matching

Coming soon.

Error Handling

Coming soon.

Generics

Coming soon.

Closures

Coming soon.

Modules

Coming soon.

npm Interop

Husk calls npm packages through explicit extern "js" declarations. The compiler emits normal JavaScript imports or requires, while Husk uses the declared types for checking calls.

Direct Module Imports

Declare a package inside an extern "js" block:

extern "js" {
    #[default]
    mod express {
        #[default]
        fn express() -> ExpressApp;
        fn json() -> Middleware;
    }
}

For scoped or hyphenated packages, give the module a Husk-safe alias:

extern "js" {
    #[default]
    mod "better-sqlite3" as better_sqlite3 {
        #[default]
        fn better_sqlite3(filename: String) -> Database;
    }
}

With --target esm, Husk emits import statements. With --target cjs, it emits require calls.

Generated Bindings

Use import-dts for one-off conversion:

huskc import-dts node_modules/@types/express/index.d.ts --module express > src/express.hk

For projects, configure dependencies in husk.toml and regenerate them:

[dts_options]
generation_gap = true
generate_report = true

[[dts]]
package = "express"
types = "@types/express"
output = "src/express.hk"
follow_imports = true
huskc dts update express --follow-imports --report

When generation_gap is enabled, generated declarations go to a .gen.hk file and the wrapper .hk file stays hand-editable.

Wrapper Pattern

Raw TypeScript declarations can be too broad for a pleasant Husk API. Prefer using generated files as input and writing a curated wrapper for the parts your program actually uses.

The examples/express_sqlite app uses this pattern:

  • generated d.ts imports document the available JS surface
  • wrapper modules define ExpressApp, Request, Response, Database, and TodoStatements
  • app code calls named helpers such as request_body_string and response_error
  • JsValue remains only at JSON and unknown-JS boundaries

JsValue Boundaries

JsValue is the escape hatch for values Husk cannot represent precisely. It is expected for untyped JSON bodies, broad TypeScript unions, any, unknown, and complex generic APIs.

Good wrappers keep JsValue local. Convert from it near the boundary with helpers like jsvalue_get, jsvalue_isNull, jsvalue_toString, and jsvalue_toNumber, then pass ordinary Husk types through the rest of the app.

CLI Reference

Coming soon.

Editor Setup

Coming soon.