Choose your own adventure: Run interpreted for rapid development or transpile to JavaScript for the entire npm ecosystem
Husk is a lightweight interpreted language that brings Rust's elegant syntax to the world of scripting and JavaScript development. Write type-safe, expressive code with the flexibility to run it instantly or compile it for production use with full access to npm's 1M+ packages.
Write scripts with Rust's clean, expressive syntax while leveraging JavaScript's massive ecosystem. No more choosing between elegant code and practical libraries.
Perfect for:
- Rapid prototyping
- Build scripts
- CLI tools
- Learning and experimentation
# Zero configuration, instant feedback
husk run script.husk
# Interactive REPL for exploration
husk repl
Perfect for:
- Web applications
- npm packages
- Node.js services
- Existing JavaScript projects
# Build once, run anywhere JavaScript runs
husk build
# Use with your favorite npm packages
npm install express
node dist/app.js
- Rust-inspired syntax - Familiar and expressive
- Static typing with inference - Catch errors early, write less boilerplate
- Pattern matching - Powerful control flow with match expressions
- Option & Result types - Built-in error handling the Rust way
- Structs & Enums - Model your data precisely
- Module system - Organize code across files
- Async/await - Modern asynchronous programming
- Type casting - Seamless conversions with
as
- Format macro - String interpolation done right
- Interactive REPL - Test ideas instantly
- Zero configuration - Just write and run
- Project build system - Scale from scripts to applications
- JavaScript interop - Use any npm package
- Fast iteration - No compile times in interpreter mode
- Cross-platform - Runs wherever Node.js runs
# Install Husk (requires Rust/Cargo)
cargo install husk-lang
// hello.husk
fn greet(name: string) -> string {
format!("Hello, {}!", name)
}
fn main() {
let message = greet("World");
println(message);
}
Run it instantly:
husk run hello.husk
Or compile to JavaScript:
husk compile hello.husk | node
To start the Husk REPL, run:
husk repl
To execute a Husk script file, use:
husk run path/to/your/script.hk
To transpile a Husk script to JavaScript, use:
husk compile path/to/your/script.hk
This will output the transpiled JavaScript code to stdout. If you have node installed you can do:
husk compile path/to/your/script.hk | node
Husk supports project-based development with husk.toml
configuration files. To build an entire project:
husk build
Additional build options:
# Generate package.json from husk.toml
husk build --generate-package-json
# Specify target platform
husk build --target node-cjs
Create a husk.toml
file in your project root:
[package]
name = "my-husk-app"
version = "0.1.0"
description = "My Husk application"
author = "Your Name"
[dependencies]
express = "^4.18.0"
lodash = "^4.17.21"
[build]
target = "node-esm"
src = "src"
out = "dist"
Place your Husk source files in the src
directory. The build command will compile all .husk
files and generate corresponding JavaScript files in the output directory.
Import maps allow you to customize module resolution, useful for CDN imports or aliasing:
[targets.browser]
platform = "browser"
format = "esm"
tree_shaking = true
[targets.browser.import_map]
react = "https://esm.sh/react@18"
"react-dom" = "https://esm.sh/react-dom@18"
lodash = "https://cdn.skypack.dev/lodash"
utils = "/utils/index.js"
shared = "./shared/lib.js"
With this configuration, imports like use external::react
will resolve to the CDN URLs.
When using import maps, especially with external URLs, consider these security aspects:
URL Validation: Husk validates import map URLs to prevent injection attacks:
- โ
Allowed:
https://
,http://
,/
,./
,../
- โ Rejected:
javascript:
,data:
,file://
, arbitrary protocols
Best Practices:
- Use HTTPS: Always prefer HTTPS URLs for external dependencies
- Pin Versions: Include specific versions in CDN URLs to prevent unexpected updates
- Trusted Sources: Only use reputable CDNs (esm.sh, skypack.dev, unpkg.com)
- Subresource Integrity: Consider using CDNs that support SRI hashes
[targets.browser.import_map]
# Good: HTTPS with pinned version
react = "https://esm.sh/[email protected]"
# Risky: No version pinning
lodash = "https://cdn.skypack.dev/lodash" # Could change unexpectedly
# Bad: HTTP is vulnerable to MITM attacks
insecure = "http://example.com/lib.js" # Avoid HTTP in production
Local Development: For development, prefer local paths over external URLs:
[targets.dev.import_map]
"@company/ui" = "./vendor/company-ui.js" # Local copy for development
Enable tree shaking to eliminate dead code in production builds:
[targets.production]
platform = "browser"
format = "esm"
tree_shaking = true # Enables dead code elimination
dev = false # Production mode (default)
[targets.development]
platform = "browser"
format = "esm"
tree_shaking = false # Disable for faster builds
dev = true # Development mode with debugging features
Development mode adds helpful debugging features:
[targets.dev]
platform = "node"
format = "esm"
dev = true # Enables:
# - Variable type comments
# - Runtime type assertions
# - Debugging hints
# - Disables tree shaking automatically
Example output in dev mode:
/* Husk variable: x (type: int) */
let x = 42;
if (typeof x !== 'number') { console.warn('Type mismatch: x expected number, got ' + typeof x); }
You can define multiple build targets for different environments:
# Browser production build
[targets.browser-prod]
platform = "browser"
format = "esm"
entry = "src/client.husk"
output = "dist/bundle.min.js"
tree_shaking = true
external = ["fs", "path", "crypto"] # Exclude Node.js modules
# Node.js CommonJS build
[targets.node-cjs]
platform = "node"
format = "cjs"
entry = "src/server.husk"
output = "dist/server.cjs"
# Development build with import maps
[targets.dev]
platform = "browser"
format = "esm"
dev = true
[targets.dev.import_map]
"@dev/logger" = "./dev/logger.js"
"@dev/debug" = "./dev/debug-utils.js"
Build specific targets:
husk build --target browser-prod
husk build --target node-cjs
husk build --target dev
Understanding how Husk's features interact helps you configure your project effectively:
- Automatic Disable: Tree shaking is automatically disabled when
dev = true
- Rationale: Development mode prioritizes debugging over optimization
- Override: You cannot enable tree shaking in dev mode (dev mode takes precedence)
[targets.dev]
dev = true
tree_shaking = true # This will be ignored - tree shaking stays disabled
- Import Map Priority: Import maps take precedence over standard package resolution
- Partial Mapping: Only mapped packages use import map URLs; others use standard resolution
[targets.browser.import_map]
lodash = "https://cdn.skypack.dev/lodash" # Uses CDN
# express not mapped - uses standard npm resolution
use external::lodash; // Resolves to https://cdn.skypack.dev/lodash
use external::express; // Resolves to node_modules/express
- Externals First: Packages marked as external are excluded from bundling
- Import Maps Apply: External packages can still use import map URLs
[targets.browser]
external = ["react", "react-dom"]
[targets.browser.import_map]
react = "https://esm.sh/react@18"
"react-dom" = "https://esm.sh/react-dom@18"
Generated imports:
import React from 'https://esm.sh/react@18'; // External + mapped
- Node.js Built-ins: Automatically external in browser builds
- Browser APIs: Not available in Node.js builds
- Import Maps: More commonly used for browser targets
[targets.browser]
platform = "browser"
# fs, path, crypto automatically marked as external
[targets.node]
platform = "node"
# Browser APIs like localStorage not available
Here's how Husk enables you to move seamlessly from rapid prototyping to production:
// calculator.husk - Quick prototype
fn calculate(operation: string, a: int, b: int) -> Result<int, string> {
match operation {
"add" => Ok(a + b),
"subtract" => Ok(a - b),
"multiply" => Ok(a * b),
"divide" => {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
},
_ => Err("Unknown operation")
}
}
fn main() {
let result = calculate("add", 10, 5);
match result {
Ok(value) => println(format!("Result: {}", value)),
Err(msg) => println(format!("Error: {}", msg))
}
}
# Test instantly during development
husk run calculator.husk
# Output: Result: 15
// web-calculator.husk - Production version with Express
use external::express;
async fn create_server() -> Result<unit, string> {
let app = express();
app.get("/calculate/:op/:a/:b", |req, res| {
let operation = req.params.op;
let a = req.params.a as int;
let b = req.params.b as int;
match calculate(operation, a, b) {
Ok(result) => res.json({"result": result}),
Err(error) => res.status(400).json({"error": error})
}
});
app.listen(3000);
println("Server running on http://localhost:3000");
Ok(())
}
async fn main() {
match create_server().await {
Ok(_) => {},
Err(e) => println(format!("Server error: {}", e))
}
}
# Build for production
husk build
npm install express
node dist/web-calculator.js
The same core logic, two different execution modes!
Here are some examples of Husk syntax:
let x = 5;
let name = "Alice";
let is_true = true;
fn add(x: int, y: int) -> int {
x + y
}
struct Person {
name: string,
age: int,
}
let p = Person {
name: "Bob",
age: 30,
};
enum Option {
Some(int),
None,
}
let opt = Option::Some(5);
match opt {
Option::Some(value) => println(value),
Option::None => println("No value"),
}
let arr = [1, 2, 3, 4, 5];
let slice = arr[1..3];
for i in 0..5 {
println(i);
}
// For loop
for x in [1, 2, 3, 4, 5] {
println(x);
}
// While loop
let i = 0;
while i < 5 {
println(i);
i = i + 1;
}
// Infinite loop with break
loop {
println("Hello");
if some_condition {
break;
}
}
Husk supports a module system for organizing code across multiple files:
// In utils.hk
pub fn log(message: string) {
println(format!("[LOG] {}", message));
}
pub fn formatDate(date: string) -> string {
format!("Date: {}", date)
}
// In main.husk
use local::utils::{log, formatDate};
fn main() {
log("Application started");
let today = formatDate("2023-12-25");
println(today);
}
Module import prefixes:
local::
- Import from project rootself::
- Import from current directorysuper::
- Import from parent directory
async fn fetchData() -> Result<string, string> {
let response = fetch("https://api.example.com/data").await?;
Ok(response.text().await?)
}
async fn main() {
match fetchData().await {
Ok(data) => println(data),
Err(error) => println(format!("Error: {}", error)),
}
}
Husk includes Option and Result types for safe error handling:
fn divide(a: int, b: int) -> Result<int, string> {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
let result = divide(10, 2)?; // Error propagation operator
Husk supports explicit type conversion using the as
operator:
// Numeric conversions
let i = 42;
let f = i as float; // 42.0
let f2 = 3.14;
let i2 = f2 as int; // 3 (truncates decimal)
// String parsing
let s = "123";
let num = s as int; // 123
// To string conversion
let n = 456;
let str = n as string; // "456"
// Boolean conversion
let b = true as int; // 1
let zero = 0 as bool; // false
let nonzero = 5 as bool; // true
Husk provides JavaScript-compatible methods for primitive types:
let s = " Hello World ";
println(s.len()); // 15
println(s.trim()); // "Hello World"
println(s.toLowerCase()); // " hello world "
println(s.toUpperCase()); // " HELLO WORLD "
let text = "Hello World";
println(text.substring(0, 5)); // "Hello"
println(text.substring(6, 11)); // "World"
let csv = "apple,banana,orange";
let parts = csv.split(","); // ["apple", "banana", "orange"]
let arr = [1, 2, 3, 4, 5];
println(arr.len()); // 5
To set up the development environment:
-
Clone the repository:
git clone https://github.com/fcoury/husk.git cd husk
-
Build the project:
cargo build
-
Run tests:
cargo test
- ๐ Website: husk-lang.org
- ๐ Documentation: husk-lang.org/docs
- ๐ฎ Playground: husk-lang.org/playground
- ๐ Examples: husk-lang.org/examples
- ๐ฌ Discussions: GitHub Discussions
- ๐ Issues: GitHub Issues
- ๐ก Feature Requests: GitHub Issues
Husk is an open-source project that welcomes contributions from everyone! Whether you're:
- ๐ Reporting bugs
- ๐ก Suggesting features
- ๐ Improving documentation
- ๐ง Writing code
- โจ Sharing examples
We'd love your help making Husk better. Check out our Contributing Guide to get started.
To set up the development environment:
# Clone the repository
git clone https://github.com/fcoury/husk.git
cd husk
# Build the project
cargo build
# Run tests
cargo test
# Install locally for testing
cargo install --path .
This project is licensed under the MIT License - see the LICENSE file for details.
Ready to choose your own adventure?
Visit husk-lang.org to get started!