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
Using Cargo (Recommended)
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:
i32i64f64boolString()
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:
JsValueJsArray<T>JsPromise<T>JsFnJsObject
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, andTodoStatements - app code calls named helpers such as
request_body_stringandresponse_error JsValueremains 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.