Skip to content

RFC-002: libuv-based IO Implementation Layer for Resource Types

References:

Abstract

This document defines the IO implementation layer for YaoXiang: providing cross-platform IO capabilities based on libuv, serving as the underlying implementation for the RFC-024 resource type system.

Core Positioning:

RFC-024: Resource Type Definitions (FilePath, HttpUrl, DBUrl, Console)
    ↓ uses
RFC-002: Resource Type IO Implementation (based on libuv)
    ↓ underlying
libuv: Cross-platform IO Engine (event loop + thread pool)

What it is NOT:

  • ❌ Not "transparent async" — users explicitly control concurrency through spawn blocks
  • ❌ Not "auto-async" — IO operations must be explicitly called within spawn blocks
  • ❌ Not "developers don't need to care about underlying details" — the resource type system ensures concurrency safety

What it IS:

  • ✅ IO implementation layer for resource types (FilePath, HttpUrl, DBUrl, Console)
  • ✅ Cross-platform IO unification (libuv handles Windows/Linux/macOS differences)
  • ✅ Shared event loop architecture (one libuv event loop handles all IO)
  • ✅ Integration with RFC-024 resource type system

Motivation

Why libuv?

RFC-024 defines the resource type system:

  • FilePath - filesystem path
  • HttpUrl - HTTP endpoint
  • DBUrl - database connection
  • Console - standard output

These resource types require underlying IO implementation. libuv provides:

Requirementlibuv Provides
Cross-platform IOUnified Windows/Linux/macOS API
Async capabilitiesShared event loop, all workers' IO centrally processed
Thread poolDedicated thread pool for blocking operations
Concurrency safetySingle-threaded event loop, inherently race-free

Relationship with RFC-024

┌─────────────────────────────────────────────────────────┐
│  RFC-024: Concurrency Model                              │
│  - spawn {} blocks (explicit concurrency)               │
│  - Resource type definitions (FilePath, HttpUrl, DBUrl, Console)
│  - Resource conflict detection (auto-serialization for same path)
└─────────────────────────────────────────────────────────┘
                          ↓ uses
┌─────────────────────────────────────────────────────────┐
│  RFC-002: Resource Type IO Implementation                │
│  - FilePath → libuv file IO                              │
│  - HttpUrl → libuv network IO                            │
│  - DBUrl → database connection pool                     │
│  - Console → standard output serialization               │
└─────────────────────────────────────────────────────────┘
                          ↓ underlying
┌─────────────────────────────────────────────────────────┐
│  libuv: Cross-platform IO Engine                        │
│  - Event loop                                           │
│  - Thread pool                                          │
│  - Cross-platform unified API                           │
└─────────────────────────────────────────────────────────┘

Proposal

1. libuv Architecture

1.1 Shared Event Loop Architecture

┌─────────────────────────────────────────────────────────┐
│                    Runtime                               │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │  Worker 0   │  │  Worker 1   │  │  Worker N   │    │
│  │  Compute    │  │  Compute    │  │  Compute    │    │
│  │  Tasks      │  │  Tasks      │  │  Tasks      │    │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘    │
│         │                │                │            │
│         └────────────────┼────────────────┘            │
│                          ↓                              │
│  ┌─────────────────────────────────────────────────┐  │
│  │          libuv Event Loop (dedicated thread)    │  │
│  │          Handles all IO operations               │  │
│  └─────────────────────────────────────────────────┘  │
│                                                         │
└─────────────────────────────────────────────────────────┘

Key Features:

  • One shared libuv event loop (running on a dedicated thread)
  • All workers' IO operations are submitted to this shared event loop
  • Single-threaded event loop inherently avoids races
  • High resource efficiency — no need to create event loops for each worker

1.2 Concurrency Safety Mechanisms

libuv FeatureYaoXiang CorrespondingConcurrency Safety
Single-threaded event loopSequential execution within spawn blocksInherently race-free
Thread pool isolationBlocking ops don't block main threadNo shared state
Async callbacksDAG scheduler manages dependenciesDeterministic execution

2. Resource Type IO Mapping

2.1 FilePath → libuv File IO

rust
// std.io module (based on libuv)
pub struct IoModule;

impl StdModule for IoModule {
    fn exports(&self) -> Vec<NativeExport> {
        vec![
            // File operations → libuv fs_* API
            NativeExport::new("read_file", "std.io.read_file",
                "(path: FilePath) -> String", native_read_file),
            NativeExport::new("write_file", "std.io.write_file",
                "(path: FilePath, content: String) -> Bool", native_write_file),
            NativeExport::new("append_file", "std.io.append_file",
                "(path: FilePath, content: String) -> Bool", native_append_file),
            // Console operations → libuv tty API
            NativeExport::new("print", "std.io.print",
                "(...args) -> ()", native_print),
            NativeExport::new("println", "std.io.println",
                "(...args) -> ()", native_println),
        ]
    }
}

// libuv file IO implementation
fn native_read_file(args: &[RuntimeValue], ctx: &mut NativeContext) -> Result<RuntimeValue, ExecutorError> {
    let path = extract_file_path(args)?;

    // Submit to libuv event loop
    // libuv reads file asynchronously
    // Return result
    ctx.uv_loop.fs_read(path)
}

2.2 HttpUrl → libuv Network IO

rust
// std.net module (based on libuv)
pub struct NetModule;

impl StdModule for NetModule {
    fn exports(&self) -> Vec<NativeExport> {
        vec![
            // HTTP operations → libuv http API
            NativeExport::new("http_get", "std.net.http_get",
                "(url: HttpUrl) -> Response", native_http_get),
            NativeExport::new("http_post", "std.net.http_post",
                "(url: HttpUrl, body: String) -> Response", native_http_post),
        ]
    }
}

// libuv network IO implementation
fn native_http_get(args: &[RuntimeValue], ctx: &mut NativeContext) -> Result<RuntimeValue, ExecutorError> {
    let url = extract_http_url(args)?;

    // Submit to libuv event loop
    // libuv async HTTP request
    // Return result
    ctx.uv_loop.http_get(url)
}

2.3 DBUrl → Database Connection Pool

rust
// std.db module (based on libuv)
pub struct DbModule;

impl StdModule for DbModule {
    fn exports(&self) -> Vec<NativeExport> {
        vec![
            // Database operations → libuv thread pool
            NativeExport::new("query", "std.db.query",
                "(url: DBUrl, sql: String) -> Rows", native_query),
        ]
    }
}

// libuv database IO implementation
fn native_query(args: &[RuntimeValue], ctx: &mut NativeContext) -> Result<RuntimeValue, ExecutorError> {
    let url = extract_db_url(args)?;
    let sql = extract_sql(args)?;

    // Submit to libuv thread pool
    // Database query executes in thread pool
    // Callback notifies main thread on completion
    ctx.uv_loop.db_query(url, sql)
}

2.4 Console → Standard Output Serialization

rust
// Console operations auto-serialized (RFC-024 resource type rules)
// All Console operations execute sequentially within the same thread
fn native_print(args: &[RuntimeValue], ctx: &mut NativeContext) -> Result<RuntimeValue, ExecutorError> {
    let output = format_args(args);

    // Console operation serialization
    // libuv tty write
    ctx.uv_loop.tty_write(output)
}

3. Integration with Spawn Blocks

3.1 User Perspective

yaoxiang
# Resource type definitions (RFC-024)
FilePath: Resource
HttpUrl: Resource

# IO operations (RFC-002 implementation)
File.read: (FilePath) -> String
HTTP.get: (HttpUrl) -> Response

# User explicit concurrency (RFC-024)
(a, b) = spawn {
    read_file("data.txt"),      # Resource type FilePath, underlying libuv
    fetch("http://example.com") # Resource type HttpUrl, underlying libuv
}
# Compiler: FilePath and HttpUrl have no conflict, can execute in parallel

3.2 Compile-time Analysis

Compiler analyzes spawn block:
1. Identify resource type operations
2. Detect resource conflicts (same path/URL auto-serialized)
3. Generate DAG execution plan
4. Mark IO nodes (submit to libuv)

3.3 Runtime Execution

Runtime executes spawn block:
1. Worker 0 submits IO task → shared event loop
2. Worker 1 submits IO task → shared event loop
3. Event loop uniformly processes all IO operations
4. IO completion notifies corresponding Worker
5. Worker continues executing subsequent tasks

4. Runtime Three-Layer Architecture and libuv

Layerlibuv UsageAsync CapabilityApplicable Scenarios
Embedded RuntimeNo libuvNo asyncWASM, game scripts
Standard RuntimeShared event loopIO asyncWeb services, data pipelines
Full RuntimeShared event loopIO async + parallelScientific computing, large-scale parallelism

Embedded Runtime: No libuv, immediate execution, no async capability.

Standard Runtime: Shared libuv event loop, all IO operations handled asynchronously.

Full Runtime: Shared libuv event loop, multi-threaded parallelism + IO async.


Detailed Design

1. Rust Bindings Structure

rust
// libuv bindings module
pub mod uv {
    // Event loop
    pub struct UvLoop {
        loop_handle: *mut uv_loop_t,
    }

    // File operations
    pub trait FileOps {
        fn fs_read(&self, path: &str) -> Result<String, UvError>;
        fn fs_write(&self, path: &str, content: &str) -> Result<(), UvError>;
        fn fs_append(&self, path: &str, content: &str) -> Result<(), UvError>;
    }

    // Network operations
    pub trait NetOps {
        fn http_get(&self, url: &str) -> Result<Response, UvError>;
        fn http_post(&self, url: &str, body: &str) -> Result<Response, UvError>;
    }

    // Database operations
    pub trait DbOps {
        fn db_query(&self, url: &str, sql: &str) -> Result<Rows, UvError>;
    }

    // Console operations
    pub trait ConsoleOps {
        fn tty_write(&self, data: &str) -> Result<(), UvError>;
    }
}

2. Standard Library Module Structure

src/std/
├── io.rs          # FilePath IO (based on libuv)
├── net.rs         # HttpUrl IO (based on libuv)
├── db.rs          # DBUrl IO (based on libuv)
├── console.rs     # Console IO (based on libuv)
└── mod.rs         # Module registration

3. Integration with DAG Scheduler

rust
// IO node interface (defined in RFC-008)
trait IoScheduler {
    // Submit IO task, return handle
    fn submit_io(&self, task: IoTask) -> IoHandle;

    // Called by libuv when IO completes, wake DAG node
    fn on_io_complete(&self, handle: IoHandle);
}

// libuv implementation
impl IoScheduler for UvLoop {
    fn submit_io(&self, task: IoTask) -> IoHandle {
        match task.resource_type {
            ResourceType::FilePath => self.fs_read(task.path),
            ResourceType::HttpUrl => self.http_get(task.url),
            ResourceType::DBUrl => self.db_query(task.url, task.sql),
            ResourceType::Console => self.tty_write(task.data),
        }
    }

    fn on_io_complete(&self, handle: IoHandle) {
        // Notify DAG scheduler to wake downstream nodes
        self.dag_scheduler.wake_dependents(handle.node_id);
    }
}

Trade-offs

Advantages

  1. Cross-platform unification: libuv handles Windows/Linux/macOS differences
  2. IO async capability: Shared event loop handles all IO, no async/await needed
  3. Concurrency safety: Single-threaded event loop inherently race-free
  4. Resource efficiency: One event loop, small memory overhead
  5. Aligned with RFC-024: Resource type system ensures concurrency safety
  6. Mature and stable: libuv validated at scale by Node.js

Disadvantages

  1. C library dependency: Requires binding to libuv C library
  2. Bootstrap limitation: May need replacement with YaoXiang native implementation after bootstrap
  3. WASM support: Requires additional adaptation work

Alternative Approaches

ApproachWhy Not Chosen
Rust std::ioSynchronous blocking, cannot cooperate with spawn blocks for async
tokioDesigned for Rust async/await, not aligned with YaoXiang's explicit concurrency model
mioOnly provides raw async primitives, lacks high-level IO functionality
Build from scratchComplex and error-prone, cannot match libuv's maturity

Implementation Strategy

Phase Breakdown

  1. Phase 1 (v0.3): libuv bindings, basic file IO
  2. Phase 2 (v0.5): Network IO, HTTP support
  3. Phase 3 (v0.7): Database IO, connection pool
  4. Phase 4 (v1.0): WASM adaptation, performance optimization

Dependencies

  • RFC-024 (concurrency model) → Completed
  • RFC-008 (Runtime architecture) → Completed
  • RFC-009 (ownership model) → Completed
  • RFC-011 (generics system) → Completed

Design Decision Records

DecisionDecisionRationaleDate
IO implementation layerlibuvCross-platform, async capability, concurrency safety2025-01-05
PositioningResource type IO implementation layerIntegration with RFC-024 resource type system2026-06-16
Event loop architectureShared event loopHigh resource efficiency, avoid duplicate creation2026-06-16
Concurrency safetySingle-threaded event loopInherently race-free, aligned with RFC-0242026-06-16
Standard library rewritestd.io/std.net based on libuvCross-platform unification, async capability2026-06-16

Open Questions

  • [ ] libuv adaptation solution for WASM environment
  • [ ] Database connection pool design
  • [ ] Complete HTTP client implementation
  • [ ] Cross-platform consistency for filesystem events
  • [ ] Timeout mechanism design for network IO
  • [ ] libuv replacement strategy after bootstrap

References

YaoXiang Official Documentation

External References


Lifecycle and Disposition

StatusLocationDescription
Draftdocs/design/rfc/draft/Under review