Rust Revisited

Rust Revisited: A Study of Similarities and Contrasts

December 28, 2021·SpeakEZ

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 a generation of language designers.

At SpeakEZ, we’ve analyzed Rust’s design choices while developing our Fidelity Framework. This exploration examines both the similarities stemming from a 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 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, an influence that continues to shape 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 such as 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.

Our Fidelity Framework acknowledges these contributions while charting its own course, drawing on F#’s twenty-year evolution. Since Don Syme’s initial development, F# has pioneered 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. Clef inherits these approaches and carries them 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

Clef’s Unified Foundation

The Clef language 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

Our Fidelity Framework is designed to extend Clef’s async model through delimited continuations, with the goal of compile-time determinism:

// async lowered to a deterministic state machine
let processWithDcont data =
    reset (fun () ->
        let validated = shift (fun k -> validateAsync data |> continueWith k)
        let transformed = shift (fun k -> transformAsync validated |> continueWith k)
        transformed
    )

Our DCont dialect in MLIR (discussed in our AMM article) is designed to generate static state machines with predetermined memory layouts and no 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)?)
}
// Clef: 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 Clef’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 { /* ... */ }

Clef’s Structural Resolution

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

Clef 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, our BAREWire design is meant to enable optimization where beneficial without polluting all code with memory concerns.

Compilation Architecture Divergence

The architectural split between Rust’s direct LLVM compilation and our MLIR pipeline carries several implications:

Rust’s Single Path

Rust → HIR → MIR → LLVM IR → Machine Code

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

Fidelity’s Multi-Level Approach

Clef → PHG → MLIR Dialects → Progressive Lowering → Multiple Targets

Our Program Hypergraph (detailed in our AMM analysis) is designed to maintain semantic information through compilation. The MLIR dialects we define, including DCont for continuations and Inet for interaction nets, are meant to target heterogeneous processors that Rust cannot efficiently reach.

This architectural difference is designed to enable:

  • 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.

Clef’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
}

Clef provides multiple concurrency patterns, including actors, streams, and 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
}

Clef’s Progressive Disclosure

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

Clef 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, built a large ecosystem, and showed how functional concepts enhance imperative programming.

Clef and our Fidelity Framework are designed to show that functional programming can target systems domains, that multiple compilation strategies can coexist, and that heterogeneous computing need not 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. Clef’s compositional abstractions appeal to those prioritizing domain logic with selective optimization.

As computing becomes more heterogeneous, spanning FPGAs, neuromorphic processors, and prospectively quantum devices, the ability to maintain multiple mental models and compilation strategies grows more valuable. That is the direction we keep building toward with Clef and our Fidelity Framework as the work continues, and we expect to keep learning from Rust’s path as we go.

For deeper exploration of specific topics: