Rust Revisited: A Study of Similarities and Contrasts

Lessons Applied to Fidelity's Emergent Path in Systems Programming
work-single-image

The Rust programming ecosystem has transformed how the software industry views systems programming. By pioneering its ownership system with “borrowing” and “lifetimes”, Rust brought compile-time memory safety into mainstream development. Beyond memory management, Rust’s innovations in zero-cost abstractions, trait-based generics, and “fearless concurrency” philosophy have influenced an entire echelon of language designers.


🔄 Updated September 2025

  • Aligned with our analysis of Abstract Machine Models and heterogeneous computing
  • Cross-referenced with detailed SRTP vs traits comparison
  • Updated MLIR dialect discussion to include DCont and Inet
  • Clarified PHG architecture and nanopass compilation strategy

At SpeakEZ, we’ve analyzed Rust’s design choices while developing the Fidelity Framework. This exploration examines both the similarities stemming from our shared OCaml heritage and the contrasts arising from different design philosophies, particularly around async programming models, ecosystem coherence, and developer ergonomics.

Honoring Rust’s Contributions

Rust deserves recognition for proving that memory safety doesn’t require sacrificing performance. Its ownership system demonstrated that imperative programming could be made dramatically safer through compile-time verification. The language’s zero-cost abstractions showed that high-level constructs could compile to machine code equivalent to hand-written C, creating a paradigm shift that continues to influence language design today.

Both Rust and F# trace their lineage to OCaml. Rust’s first compiler was written in OCaml, leaving traces in its pattern matching and type system design. F# began as an explicit adaptation of OCaml for .NET, later spawning the Fable compiler for JavaScript transpilation. This shared heritage manifests in similar constructs - algebraic data types, pattern matching, and functional influences - though the languages pursued divergent paths. Where F# embraced functional programming with immutability by default, Rust chose an imperative foundation with ownership-based safety.

The Fidelity Framework acknowledges these contributions while charting its own course, building on F#’s twenty-year evolution. Since Don Syme’s initial development, F# has pioneered unique approaches: units of measure, type providers, computation expressions, and statically resolved type parameters (detailed in our companion analysis). F# also synthesized OCaml’s type safety with Erlang’s actor model through MailboxProcessor, creating capabilities that Fidelity now extends into native compilation.

The Async Runtime Divergence

Rust’s ecosystem maturity provides valuable lessons about system design at scale, particularly visible in its async evolution.

Rust’s Runtime Fragmentation

While Rust’s async/await syntax appears clean, the underlying reality involves runtime fragmentation that impacts the entire ecosystem:

// Surface simplicity hiding runtime complexity
async fn fetch_and_process(url: &str) -> Result<Data, Error> {
    let response = fetch_url(url).await?;
    let processed = process_data(response).await?;
    Ok(processed)
}

// Reality: Which runtime? Tokio? async-std? smol?
// Each has different scheduling, performance, debugging

This creates cascading challenges:

  1. Ecosystem Lock-in: Choose Tokio, and your entire dependency tree must align
  2. Opaque Debugging: State machine transformations obscure stack traces
  3. Library Fragmentation: Authors maintain multiple implementations per runtime

F#’s Unified Foundation

F# has maintained a single async model since 2007, predating most language async implementations:

// Consistent async model across all contexts
let fetchAndProcess url = async {
    let! response = fetchUrl url
    let! processed = processData response
    return processed
}

This unity provides predictable execution, clear debugging, and universal compatibility - benefits explored in depth in our AMM analysis.

Fidelity’s Compile-Time Innovation

Fidelity extends F#’s async model through delimited continuations, achieving compile-time determinism:

// Standard F# async becomes deterministic state machines
let processWithDcont data =
    reset (fun () ->
        let validated = shift (fun k -> validateAsync data |> continueWith k)
        let transformed = shift (fun k -> transformAsync validated |> continueWith k)
        transformed
    )

The DCont dialect in MLIR (discussed in our AMM article) enables static state machine generation with predetermined memory layouts and zero runtime overhead.

Converging on Error Handling

Both languages independently arrived at Result-based error handling with monadic composition:

// Rust: Result with ? operator
fn process_file(path: &str) -> Result<Summary, ProcessError> {
    let contents = std::fs::read_to_string(path)?;
    let parsed = parse_contents(&contents)?;
    let validated = validate_data(parsed)?;
    Ok(summarize(validated)?)
}
// F#: Result with computation expressions
let processFile path = result {
    let! contents = File.readAllText path
    let! parsed = parseContents contents
    let! validated = validateData parsed
    let! summary = summarize validated
    return summary
}

Both maintain type safety and composability, though F#’s computation expressions provide more flexibility for custom control flow.

Type System Philosophies

While traits vs SRTP deserves its own detailed analysis, the fundamental difference impacts daily development:

Rust’s Explicit Implementations

trait Process {
    fn process(&self) -> Result<(), Error>;
}

// Must implement for each type
impl Process for DataTypeA { /* ... */ }
impl Process for DataTypeB { /* ... */ }

F#’s Structural Resolution

// Constraints emerge from usage
let inline process x =
    x.Process()  // Works for any type with Process member

F# infers constraints from usage patterns, reducing boilerplate while maintaining type safety.

Memory Management Philosophies

Both achieve memory safety through different mechanisms:

Rust’s Mandatory Ownership

fn process<'a>(data: &'a mut Vec<Data>) -> Result<&'a Stats, Error> {
    // Lifetime annotations permeate signatures
    // Ownership rules enforced everywhere
}

Fidelity’s Optional Engagement

// Clean signatures focused on intent
let process (data: Data list) : Result<Stats, Error> =
    // Memory optimization available when needed
    // Not mandatory in every function

As detailed in Memory Management by Choice, BAREWire enables optimization where beneficial without polluting all code with memory concerns.

Compilation Architecture Divergence

The architectural split between Rust’s direct LLVM compilation and Fidelity’s MLIR pipeline has profound implications:

Rust’s Single Path

Rust → HIR → MIR → LLVM IR → Machine Code

Direct LLVM compilation provides excellent optimization but locks into a single lowering strategy.

Fidelity’s Multi-Level Approach

F# → PHG → MLIR Dialects → Progressive Lowering → Multiple Targets

The Program Hypergraph (detailed in our AMM analysis) maintains semantic information through compilation. MLIR’s dialects - including DCont for continuations and Inet for interaction nets - enable targeting heterogeneous processors that Rust cannot efficiently reach.

This architectural difference enables:

  • Cross-domain optimizations across async, memory, and parallel operations
  • Hardware-specific lowering (FPGA dataflow, neuromorphic spikes, GPU kernels)
  • Preservation of mathematical properties for algebraic optimization
  • Alternative compilation paths beyond LLVM

Concurrency Models

Rust’s Ownership-Based Safety

use std::sync::Arc;
use rayon::prelude::*;

fn process_concurrent(data: Vec<Data>) -> Vec<Result<Output, Error>> {
    data.par_iter()
        .map(|item| process_single(item))
        .collect()
}

Ownership prevents data races through compile-time verification.

F#’s Compositional Patterns

// Multiple concurrency patterns unified through capabilities
let processConcurrent data = async {
    // Actor-based isolation
    let! actor = spawnProcessor()
    
    // Parallel streams with backpressure
    let! results = 
        data
        |> ColdStream.ofSeq
        |> ColdStream.mapAsync process
        |> ColdStream.parallel maxConcurrency
        
    return results
}

F# provides multiple concurrency patterns - actors, streams, parallel collections - unified through consistent async semantics.

Developer Experience Contrasts

Rust’s Explicit Control

use std::rc::Rc;
use std::cell::RefCell;

struct State {
    data: Rc<RefCell<Vec<Item>>>,  // Every decision visible
}

F#’s Progressive Disclosure

type State = {
    Data: Item list  // Complexity introduced only when needed
}

F# provides clean abstractions with escape hatches for optimization, while Rust requires upfront engagement with all complexity.

Philosophical Divergence

The fundamental split reflects different philosophies about systems programming:

Rust’s Approach:

  • Make everything explicit for total control
  • Safety through ownership and lifetimes
  • One mental model (AMM) with strict rules
  • Direct compilation to maintain predictability

Fidelity’s Approach:

  • Clean abstractions with optional depth
  • Safety through immutability and capabilities
  • Multiple AMMs selected by context (as explored in our AMM analysis)
  • Multi-level compilation preserving semantics

Looking Forward

Both languages contribute valuable perspectives to systems programming:

Rust proved that memory safety without garbage collection is practical, created a powerful ecosystem, and showed how functional concepts enhance imperative programming.

F#/Fidelity demonstrates that functional programming can target systems domains, that multiple compilation strategies can coexist, and that heterogeneous computing doesn’t require sacrificing high-level abstractions.

Rather than viewing these as competing approaches, they represent complementary explorations of the design space. Rust’s explicit control appeals to developers wanting complete visibility. F#’s compositional abstractions appeal to those prioritizing domain logic with selective optimization.

The future of systems programming isn’t monolithic - it’s an ecosystem of languages pushing boundaries in different directions. As computing becomes increasingly heterogeneous (FPGAs, neuromorphic processors, quantum devices), the ability to maintain multiple mental models and compilation strategies becomes essential. Both Rust and Fidelity contribute important pieces to this evolving puzzle.

For deeper exploration of specific topics:

Author
Houston Haynes
date
December 28, 2021
category
Analysis

We want to hear from you!

Contact Us