The full scope across this batch: POST /sessions key→token exchange, SessionStore dual-index with revoke_by_id, CLI Bearer injection (ONTOREF_TOKEN), ontoref setup --gen-keys, install scripts, daemon config form roundtrip, ADR-004/005, on+re self-description update (fully-self-described), and landing page refresh.
30 KiB
theme, title, titleTemplate, layout, keywords, download, exportFilename, monaco, remoteAssets, selectable, record, colorSchema, lineNumbers, themeConfig, fonts, drawings, scripts, background, class
| theme | title | titleTemplate | layout | keywords | download | exportFilename | monaco | remoteAssets | selectable | record | colorSchema | lineNumbers | themeConfig | fonts | drawings | scripts | background | class | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| default | Why I Needed Rust | %s - Rustikon 2026 | cover | Rust,programming | true | Rustikon_2026_Why_I_Need_Rust | true | true | true | true | dark | false |
|
|
|
|
./images/charles-assuncao-1BbOtIqx21I-unsplash.jpg | justify-center flex flex-cols photo-bg |
Why I Needed Rust
Finally, Infrastructure Automation I Can Sleep On
Years. One Problem.
1987 → 2025
| Era | Tool | Lesson |
|---|---|---|
| 1990s | Perl | Power without safety is a disaster |
| 2000s | Python | Pragmatism without guarantees is fragile |
| 2010s | Bash · Chef · Ansible · Terraform | More tools don't solve paradigm problems |
| 2020s | Go · ??? |
Each time, reality proved me wrong.
layout: cover background: ./mYBMP8pW4uQ.webp class: 'text-center photo-bg'
The Evolution
How we got here
Stage 1 — Local (late 80s / early 90s)
Dumb terminals. Single machine. One state.
- Local development, long deployment cycles, low urgency
- One state — easy to observe, easy to control
- IaC: procedural scripts, logic hidden inside the application
The Perl Era: we could do anything.
We could also break anything.
Beautiful, terrifying metaprogramming.
No safety net.
Silent failures at 3 AM.
Lesson: power without safety is a disaster.
Stage 2 — Networks / Internet
Systems getting farther away. More people. More coordination.
- Remote access, distributed teams, security becomes relevant
- Cost of downtime rises — processes become critical
- Harmonizing: package installs, config, updates across multiple machines in parallel
- IaC: reproducible automation, first declarative attempts
The Python Era: rapid development, great community.
But nothing stopped you from being wrong.
Type hints came late — and optional.
Runtime errors >> compile-time errors.
Lesson: pragmatism without guarantees is fragile.
More pieces. More people. Getting interesting.
Stage 3 — Containers / Cloud / CI-CD
Everything. Everywhere. All at once.
- Monolith → distributed, 24×7×365, high availability
- Cloud, hybrid, multi-cloud, on-prem — simultaneously
- Rollback and rollforward: database transactions, but for infrastructure
- Scale horizontally AND vertically — and descale
- CI/CD continuous: new features, new deploys, permanent churn
The Cloud/IaC Era: Ansible, Terraform, Chef, Puppet.
What changed? The syntax.
What didn't? The fundamental problems.
Still fighting type safety. Still discovering errors in production.
Lesson: more tools don't solve paradigm problems.
layout: cover background: ./3XXSKa4jKaM.webp
I could automate infrastructure.
But I couldn't make it reliable.
I couldn't prevent mistakes.
I couldn't sleep.
layout: cover class: text-center
Why IaC Fails
The restaurant problem
layout: two-cols class: 'restaurant bg-photo'
The Restaurant
Every restaurant has at least three actors.
| Restaurant | Infrastructure |
|---|---|
| Guest declares what they want |
Declarative config (YAML, HCL) |
| Waiter validates and transmits |
Orchestrator (K8s, Ansible) |
| Kitchen executes and delivers |
Runtime / provisioning |
| Dish arrives — or doesn't |
Deployment succeeds — or not |
::right::
What makes it work — or not:
The guest declares. Doesn't implement.
The waiter must know what's possible —
before going to the kitchen.
"I want X" → waiter goes to kitchen
→ "we don't have X, why is it on the menu?"
→ back to the table.
Equivalent: I configured a host with port 8443
→ that port isn't allowed
→ reconfigure from zero.
layout: two-cols
The Truth That Mutates
State is not static.
It can change at every step of the chain.
| Step | Truth for this actor |
|---|---|
| Guest speaks | What they want |
| Waiter's notepad | What was written down |
| Kitchen markings | What's done / not done |
| Payment ticket | What was actually served |
Fail early = fail cheap. Fail in production = nightmare.
::right::
The context problem:
The waiter knows the regular customer:
"always no salt."
The kitchen doesn't. If the waiter changes
— that context disappears.
Configuration drift is the same thing: Implicit state. Not explicit. Not propagated. Lost silently.
The cost of failure depends on where it happens:
- Fail at the table (impossible order):
cheap — caught before kitchen - Fail in kitchen (ingredient missing):
medium — renegotiate with guest - Fail at delivery (wrong dish arrives):
expensive — experience destroyed
"We Don't Have Mushrooms"
When an actor in the chain can't fulfill part of the order.
"Can I substitute vegetables?"
That renegotiation must be explicit. Traced. Re-authorized.
Not silent. Not assumed.
Configuration drift is silent renegotiation:
The system changes. Nobody notified. State diverges without trace.
Rust's answer — Option<T>:
// The waiter cannot silently skip a missing ingredient
let mushrooms: Option<Ingredient> = order.mushrooms;
match mushrooms {
Some(m) => add_to_dish(m),
None => renegotiate_with_guest(&guest)?, // explicit. always.
}
// drift = treating None as Some. Rust makes that impossible.
The compiler is the waiter who cannot pretend an ingredient exists.
The Config Evolution
How we got from code to YAML hell
-
Hardcoded — everything inside the binary. Full control. Zero flexibility.
-
External config (JSON) — works between machines. Unreadable for humans at scale.
-
YAML / TOML — more readable. Fragile syntax. Implicit types. Silent errors.
-
YAML + Serde — Serde validates the structure:
- Does the field exist? Is it the right type?
- Do we accept
"elephant"as a pet? If the type isString... yes. - Serde validates shape. Not meaning.
-
Helm / Jinja templates — YAML generated from variables (in YAML).
- Does it validate the content of the generated YAML? No. Not at all.
- Like using an LLM with a markdown reference: the format is there,
but is the content correct?
Nobody guarantees that.
layout: cover background: ./3XXSKa4jKaM.webp
Continuous CI/CD.
No semantic validation.
Continuous hope.
(crossing our fingers in production)
class: items-list
Three Questions Without Answers
- "Works on my machine" — in production, I don't know
- Fail late = maximum cost. We want: fail fast, fail cheap
- Is the declaration sufficient and consistent with what's possible?
- What are the boundaries? Static or dynamic? What is the source of truth — and when does it mutate?
- CI/CD without semantic validation = continuous hope
- We want certainty, not randomness
- "Works on my machine" cannot be the production standard
We're not inventing anything new. Everything already exists. The question is whether we're managing it correctly.
layout: cover background: /jr-korpa-vg0Mph2RmI4-unsplash.jpg
The tools weren't the problem.
The languages weren't the problem.
The paradigm was the problem.
layout: cover background: andrew-neel-jtsW--Z6bFw-unsplash.jpg
Systems we don't know how to control.
We hope they work.
When they don't — we fix them.
Continuous nightmare.
( alarm state as the new normal)
layout: section
The answer to all three questions
The Bridge: From Serde to Types
Serde loads structurally valid config.
But "elephant" as pet: String compiles.
Rust's answer: don't use String. Use a type.
// Before: String — anything goes
pet: String // "elephant" compiles. "unicorn" compiles. 🤷
// After: closed domain — impossible values don't exist
enum Pet { Dog, Cat, Rabbit } // "elephant" doesn't compile
This is the shift.:
Not the config format. The model of what it can contain.
Serde validates shape | Types validate meaning
The compiler validates before the binary exists.
What Rust Gives Us
// Immutability by default — invariants are invariants
let config = load_config()?; // cannot change silently
// Option<T> — no nulls, no assumptions
let mushrooms: Option<Ingredient> = order.mushrooms;
match mushrooms {
Some(m) => add_to_dish(m),
None => notify_kitchen_to_skip(), // explicit. always.
}
// Enums as closed domains
enum CloudProvider { Hetzner, UpCloud, AWS, GCP, Azure, OnPrem }
enum Port { Valid(u16) } // not any integer — a valid port
// Traits define what every actor in the chain must fulfill
#[async_trait]
pub trait TaskStorage: Send + Sync {
async fn create_task(&self, task: WorkflowTask) -> StorageResult<WorkflowTask>;
async fn update_task(&self, id: &str, status: TaskStatus) -> StorageResult<()>;
// Add a new provider: implement this trait or it doesn't compile
}
layout: two-cols
The Compiler as Pre-Validator
// Closed domain — you can't forget a case
enum RollbackStrategy {
ConfigDriven,
Conservative, // preserve unless marked deletion
Aggressive, // revert all changes
Custom { operations: Vec<String> },
}
// The compiler enforces exhaustive handling
match strategy {
RollbackStrategy::ConfigDriven => ...,
RollbackStrategy::Conservative => ...,
RollbackStrategy::Aggressive => ...,
RollbackStrategy::Custom { .. } => ...,
// miss one → compile error
}
::right::
The compiler validates:
- Before building the binary
- Not after hours of execution
- Not when a function nobody touched in months finally gets called
- Predictable behavior:
memory, resources, workflows
Before it reaches the kitchen.
Before the guest waits.
Before any ingredient is missing.
layout: cover background: clouds_lpa.jpg
The Human Impact
When the system is trustworthy:
✓ Sleep comes back
✓ Confidence returns
✓ The team trusts the automation
✓ Stress decreases
✓ You can actually rest
What you can't measure: fear.
What you can measure: MTTR.
Before: > 30 minutes. Now: < 5 minutes.
layout: cover background: /jude-infantini-mI-QcAP95Ok-unsplash.jpg
Continuous CI/CD.
Types. Compiler. Explicit state.
Continuous certainty.
(to keep sleeping well)
layout: section
This is not theory
layout: center name: provisioning
layout: two-cols
Nickel
YAML rejected. TOML rejected.
Reason: no type safety.
YAML wrote what we wanted.
It couldn't say what was possible.
Nickel closes that gap
— at config time, not at 3 AM.
# Infrastructure schema
# — validated at config compile time
{
compute | {
region | String,
count | Number & (fun n => n > 0),
scaling | {
min | Number & (fun n => n > 0),
max | Number & (fun n => n >= min),
# -- compiler verifies this relationship
}
}
}
::right::
Typed Source of Truth
Result (ADR-003):
zero configuration type errors in production.
Config hierarchy:
defaults → workspace → profile →
environment → runtime
Each layer merges.
Type system catches conflicts.
At config time — not deployment time.
The guest wrote an impossible order.
Nickel makes impossible orders unwritable.
Serde validates shape.
Nickel validates meaning.
The compiler validates before deployment.
layout: two-cols
Traits as Provider
The kitchen can change.
AWS ≠ UpCloud ≠ bare metal. Same menu.
// Every provider implements the same contract
enum DependencyType { Hard, Soft, Optional }
enum TaskStatus {
Pending, Running, Completed, Failed, Cancelled
}
// Dependency resolution
// — the orchestrator knows the order
// Installing Kubernetes:
// containerd (Hard) → etcd (Hard) → kubernetes
// → cilium (requires kubernetes)
// → rook-ceph (requires cilium)
Explicit state — no drift:
pub struct WorkflowExecutionState {
pub task_states: HashMap<String, TaskExecutionState>,
// what happened and when
pub checkpoints: Vec<WorkflowCheckpoint>,
pub provider_states: HashMap<String, ProviderState>,
}
::right::
Contracts
- Checkpoint every 5 minutes
- No implicit state.
No "the waiter remembers the customer doesn't want salt."
- It's in the order. Always. Explicit.
layout: two-cols class: -mt-7
Dependency Graph
Fail_fast: bool is not a config option.
It's a principle encoded as a type.
Typed DAG — dependency resolution enforced
at workflow compile time:
The kitchen doesn't serve the main course
before the starter is done.
DependencyType::Hard is that rule.
In the type system, not in a runbook.
pub struct WorkflowConfig {
pub max_parallel_tasks: usize,
pub task_timeout_seconds: u64,
// halt on first failure
pub fail_fast: bool,
// recovery point granularity
pub checkpoint_interval_seconds: u64,
}
containerd (Hard) → etcd (Hard) → kubernetes
→ cilium (requires: kubernetes)
→ rook-ceph (requires: kubernetes + cilium)
::right::
Fail Fast, Fail Cheap
DependencyType::Hard
- failure stops the chain. Always.DependencyType::Soft
- continues, explicitly degraded.DependencyType::Optional
- missing is expected and fine.
The compiler catches the install order.
Not the on-call engineer at 2 AM.
layout: two-cols
Real Applications
Kubernetes
The orchestrator provisions cluster
components as a typed workflow:
containerd
→ etcd
→ kubernetes control plane
→ CoreDNS
→ Cilium (CNI)
→ Rook-Ceph (storage)
Each dependency is a DependencyType.
The compiler catches:
installing Cilium without Kubernetes.
Not the on-call engineer at 2 AM.
"In my machine it works" has a price here.
This is the highest-stakes infrastructure in the deck.
::right::
Blockchain Validators
Validators require brutal uptime.
A validator that fails loses funds — not your infrastructure's money.
Your customer's.
- Post-quantum cryptography: CRYSTALS-Kyber + Falcon + AES-256-GCM hybrid. Validator keys protected against quantum computers.
- SLOs with real error budgets: 99.99% = 52.6 min downtime/year. Prometheus blocks deploys when burn rate exceeds budget.
- Deterministic config: validator parameters are types. A
bond_amountthat isn't a validu128doesn't compile.
layout: two-cols
Disaster Recovery
Rollback as a type, not a procedure
3 AM. Something broke. You need to rollback.
Without types: you improvise.
With types: you choose a strategy
— or it doesn't compile.
// Checkpoint = complete system snapshot
pub struct Checkpoint {
pub workflow_state: Option<WorkflowExecutionState>,
pub resources: Vec<ResourceSnapshot>,
pub provider_states: HashMap<String, ProviderState>,
}
// Rollback strategy = typed choice, not a runbook
enum RollbackStrategy {
ConfigDriven,
Conservative, // preserve unless marked for deletion
Aggressive, // revert all changes
Custom { operations: Vec<String> },
}
// You cannot do rollback without choosing a strategy.
// The compiler doesn't let you ignore the case.
::right::
Multi-backend backup: restic, borg, tar, rsync
— all as enum variants.
Production backup and DR restore use the same type, the same schema.
The runbook exists.
Nobody reads it clearly at 3 AM under pressure.
The type forces the decision before the crisis.
The state is the same in prod and in DR. Always.
layout: two-cols
Self-Healing
When something breaks at 3 AM
— the system responds, not you.
```rust enum RemediationAction { ScaleService { service: String, replicas: u32 }, FailoverService { service: String, region: Region }, RestartService { service: String }, ClearCache { service: String, scope: CacheScope }, } // Typed playbooks. Not shell scripts. Not hope. // Fails 3 times → escalates to human. // Never loops indefinitely. ```
::right::
— Typed Remediation
What happens at 3 AM:
-
Alert fires
→RemediationEnginematches condition
→ runsRestartService -
Works: silent. Nobody woken up.
-
Fails 3×: page sent
— with full state, checkpoint,
and execution history.
You wake up to information. Not to chaos.
layout: cover background: /jude-infantini-mI-QcAP95Ok-unsplash.jpg class: photo-bg
Without types. Without compiler. Without explicit state.
MTTR > 30 minutes.
Rust. Types. Explicit state. Automated response.
MTTR < 5 minutes.
(at 3 AM. without you.)
layout: two-cols
Why This Matters
For everyone in this room
::right::
layout: two-cols
For You
If you've been frustrated like me
- Rust solves problems you already have.
- This is not hype. It is operational relief.
Start here:
- Model your infrastructure as types
- Let the compiler pre-validate before deployment
::right::
If you're earlier in your career
- Start with type safety from day one.
- Build for reliability, not only speed.
The shortest path:
- Types for config.
- Traits for providers.
- Determinism for operations.
layout: cover background: ./mYBMP8pW4uQ.webp class: 'text-center photo-bg'
I have perspective from long production experience.
I have seen technologies come and go.
Rust is not hype. Rust is relief with evidence.
It solves real operational problems I had for decades.
More years is not a liability.
It is an advantage.
layout: cover name: end background: ./images/cleo-heck-1-l3ds6xcVI-unsplash.jpg class: 'photo-bg'
Why I Needed Rust
Rust gave me deterministic systems and better sleep.
Start small: model infrastructure as types.
More info:
· jesusperez.pro
· provisioning.systems · vapora.dev · rustelo.dev