Write familiar
@if,@foreach,@extends,{{ $var }}, and<x-component>Blade templates — executed directly at native Rust speed.
Overview · vs Tera · Quickstart · Try/Catch · Native Plugins · Blade Reference · Components · Hot Reload
zeno-rs brings the developer experience of Laravel Blade templating to the Rust ecosystem. If you are familiar with Blade syntax, you can use your existing .blade.zl views seamlessly in Rust web services (such as Axum, Actix, or Tower) without learning a new template syntax.
- 100% Blade Compatible: Supports
@extends,@section,@yield,@include,@forelse,@csrf,@method, and<x-component>tags. - Smart Hot Reload: Per-file
mtimecache invalidation so template edits take effect instantly in development without restarting the server. - Embedded ZenoLang Execution: Full scripting engine with 50+ built-in slots for string manipulation, math calculations, arrays, and maps.
- Native Try/Catch Error Handling: First-class
try/catchslot for safe execution with$errorvariable injection (new inv0.2.3). - Native Dynamic Plugin System: Load compiled Rust shared libraries (
.so,.dylib,.dll) at runtime via FFI without recompiling the core engine. - Lightweight Footprint: Single binary deployment with minimal memory consumption (~2-5 MB under load).
Tera is a popular template engine in the Rust ecosystem inspired by Jinja2/Django. zeno-blade is designed specifically for teams that prefer Laravel Blade syntax or require granular per-file hot reloading.
| Feature | zeno-blade | Tera | Notes |
|---|---|---|---|
| 🔥 Per-file Hot Reload | ✅ | ❌ | zeno-blade re-parses only modified files on save |
| 🎨 Laravel Blade Syntax | ✅ | ❌ | Direct support for @extends, @section, {{ $var }} |
🧩 HTML Components (<x-component>) |
✅ | ❌ | Native component encapsulation with slots |
| 📐 Layout Inheritance | ✅ | ✅ | Both support layout inheritance & block yields |
🔁 Empty Loop Fallback (@forelse) |
✅ | ❌ | Direct support for @forelse ... @empty ... @endforelse |
🎯 Conditional Classes (@class) |
✅ | ❌ | Directive for dynamic CSS class lists |
🔐 Form Directives (@csrf, @method) |
✅ | ❌ | Built-in form helpers |
| 🛡️ Native Try/Catch Error Handling | ✅ | ❌ | First-class try/catch with $error injection |
🔌 Native C-ABI Dynamic Plugins (.so) |
✅ | ❌ | Load .so/.dylib Rust extensions at runtime |
| 🧰 Built-in Utility Suite | ✅ | ❌ | 50+ built-in math, string, array, map slots |
| 📄 OpenAPI / Swagger UI Integration | ✅ | ❌ | Bundled zeno-apidoc crate |
zeno-rs is organized as a modular workspace:
zeno-rs/
├── crates/
│ ├── zenocore/ # 🔩 Core engine: lexer, parser, executor, scope, dynamic plugins
│ ├── zeno-blade/ # 🎨 Blade engine — transpiles .blade.zl → AST → HTML
│ ├── zeno-std/ # 🧰 Standard library: math, date, string, money
│ ├── zeno-apidoc/ # 📄 OpenAPI 3.0 spec + Swagger UI generator
│ ├── zenoengine/ # 📦 Batteries-included facade (recommended starting point)
│ └── zeno-plugin-example/ # 🔌 Example native C-ABI Rust dynamic plugin (.so)
└── examples/
└── web_server/ # 🚀 Axum web server integration example
Templates are 100% portable between Go (
zeno-go) and Rust (zeno-rs) backends.
Add zenoengine to your Cargo.toml:
[dependencies]
zenoengine = "0.2.3" # batteries-included facade
zenocore = "0.2.3" # core engine + plugin system
zeno-blade = "0.2.3" # or just the Blade engineuse zenoengine::{new_engine, executor::Context, scope::{Scope, Value}};
use zeno_blade::{register_blade_slots, slots::HtmlBuffer};
use zenocore::parser::parse_string;
let engine = new_engine();
register_blade_slots(&engine);
let mut ctx = Context::new();
ctx.set("httpWriter", HtmlBuffer(std::sync::Mutex::new(String::new())));
let scope = Scope::new(None);
scope.set("_view_root", Value::String("resources/views".to_string()));
scope.set("user", Value::String("Alex".to_string()));
scope.set("title", Value::String("Dashboard".to_string()));
let node = parse_string("view.blade: 'dashboard'", "main.zl").unwrap();
engine.execute(&mut ctx, &node, &scope).unwrap();
let html = ctx.get::<HtmlBuffer>("httpWriter").unwrap();
println!("{}", html.0.lock().unwrap()); // Rendered HTML output@extends('layouts.app')
@section('content')
<h1>Welcome back, {{ $user }}!</h1>
@if($role == 'admin')
<span class="badge badge-admin">Admin</span>
@endif
@forelse($posts as $post)
<article><h2>{{ $post }}</h2></article>
@empty
<p>No recent posts.</p>
@endforelse
@endsectionNew in v0.2.3 — Native
try/catchslot built intozenocore.
ZenoLang now includes a first-class try slot for safe, graceful error handling in any .zl script. When execution inside the run block fails, the engine automatically populates the $error variable with the diagnostic message and jumps to the catch block — without crashing the process.
try: {
run: {
# Code that may fail
io.file.read: '/etc/config.json' { as: $config }
json.parse: $config { as: $data }
}
catch: {
# $error is automatically set to the error message
log: "Caught error: ${error}"
http.bad_request: { success: false, message: "Config load failed: ${error}" }
}
}| Behavior | Detail |
|---|---|
run block |
All statements executed in sequence; stops at the first error |
catch block |
Executed only when run fails; skipped on success |
$error variable |
Automatically set to the Diagnostic.message string |
| Propagation | Errors in catch itself will propagate normally |
| Nesting | try blocks can be nested inside other try blocks |
File I/O with fallback:
try: {
run: {
io.file.read: $config_path { as: $raw }
json.parse: $raw { as: $cfg }
}
catch: {
log: "Warning: using defaults — ${error}"
var: $cfg { val: { debug: true } }
}
}Database operation with error response:
try: {
run: {
db.select: "SELECT * FROM users WHERE id = ?" {
bind: [$user_id]
first: true
as: $user
}
http.ok: { data: $user }
}
catch: {
http.bad_request: { success: false, message: "Database error: ${error}" }
}
}zeno-rs includes a Native Dynamic Plugin System — one of ZenoLang's most powerful features. You can compile custom Rust code into a shared library (.so, .dylib, .dll) and load it dynamically into ZenoCore at runtime, without recompiling the core engine.
As of v0.2.3, ZenoLang fully supports native plugins with:
| Capability | Description |
|---|---|
| 🔄 Runtime Loading | Load .so/.dylib/.dll at runtime via plugin.load: |
| 🧩 Custom Slots | Register new slots callable from any .zl script |
| 📦 Scope Access | Read & write the ZenoLang Scope from plugin code |
| 🔗 C-ABI Stable Interface | Safe FFI boundary via extern "C" entry point |
| ♻️ Hot-pluggable | Multiple plugins can be loaded in one script session |
// Cargo.toml -> [lib] crate-type = ["cdylib"]
use zenocore::{Engine, Value, SlotMeta};
use std::sync::Arc;
#[unsafe(no_mangle)]
pub extern "C" fn zeno_plugin_init(engine: &Engine) {
engine.register(
"custom.sha256",
Arc::new(|engine, _ctx, node, scope| {
let input = engine.resolve_shorthand_value(node, scope).to_string_coerce();
let hash = format!("{:x}", md5::compute(input.as_bytes()));
scope.set("hash_result", Value::String(hash));
Ok(())
}),
SlotMeta::default(),
);
}# Load shared library dynamically at runtime
plugin.load: './plugins/libcustom_plugin.so'
# Execute custom slot registered by the plugin
custom.sha256: 'secret data' {
as: $hash
}
log: $hashplugin.load: './plugins/libimage_processor.so'
plugin.load: './plugins/libpayment_gateway.so'
image.resize: $original_path {
width: 800
height: 600
as: $resized
}
payment.charge: $amount {
currency: 'USD'
as: $result
}
log: $result💡 See
crates/zeno-plugin-examplefor a full working plugin implementation.
| Directive | Description |
|---|---|
{{ $var }} |
Escaped output |
{!! $raw !!} |
Unescaped raw HTML output |
@if / @elseif / @else / @endif |
Conditional branches |
@foreach / @endforeach |
Array/list iteration |
@forelse / @empty / @endforelse |
Iteration with empty fallback block |
@extends('layout') |
Extend base template layout |
@section / @endsection |
Define content section |
@yield('name') |
Render content section in layout |
@include('partial') |
Include partial template |
@push('stack') / @stack('stack') |
Push to named stack |
@class(['cls' => $cond]) |
Dynamic CSS class builder |
@csrf / @method('PUT') |
Form helpers |
{{-- comment --}} |
Server-side comment |
Encapsulate UI into reusable Blade components (<x-component>):
Component definition — resources/views/components/alert.blade.zl:
<div @class(['alert', 'alert-danger' => $is_danger, 'alert-success' => $is_success])>
<strong>{{ $header }}</strong>
<p>{{ $slot }}</p>
</div>Usage:
<x-alert :is_danger="true">
<x-slot name="header">Notice</x-slot>
Your session will expire shortly.
</x-alert>In development, zeno-blade checks the mtime timestamp of template files on each request. Only files that have been modified are re-parsed into AST. All unchanged templates remain cached in memory, providing instant feedback without restarting the server.
ZenoCore includes 50+ built-in slots across 5 categories:
- Logic:
if(&&,||,==,!=,>,<,>=,<=),for,while,var,fn,call - Error Handling:
try/catchwith$errorinjection (new in v0.2.3) - String:
string.trim,upper,lower,split,replace,contains,starts_with,ends_with,len,concat,substr,format - Math:
math.add,sub,mul,div,mod,pow,sqrt,abs,ceil,floor,round,min,max,clamp,random - Collections:
array.push,pop,shift,unshift,slice,reverse,sort,unique,contains,map.set,get,delete,merge,keys,values,has,entries - Utilities:
log,print,coalesce,cast.to_int/float/string/bool,include,json.parse,json.stringify,util.datetime,util.timestamp,util.uuid,util.env,time.sleep
git clone https://github.com/nextcore/zeno-rs
cd zeno-rs
# Build plugin example
cargo build --package zeno-plugin-example
# Run workspace unit tests (includes try/catch tests)
cargo testRequirements: Rust 1.85+ (Edition 2024)
- ✨ New: Native
try/catchslot inzenocorewithrun/catchblock syntax - ✨ New:
$errorvariable automatically injected on failure - ✅ Added 3 unit tests for try/catch behavior
- 🔢 Bump all crates to
0.2.3
- Native dynamic plugin system (
.so/.dylib/.dll) - Full Blade component & layout inheritance support
- OpenAPI/Swagger UI via
zeno-apidoc
Apache 2.0 © NextCore
Laravel Blade DX powered by Rust Performance.
⭐ Star zeno-rs on GitHub if you find this project useful!