Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Type Safety and Shared Definitions

Loading…

Type Safety and Shared Definitions

Relevant source files

Purpose and Scope

This document explains how muxio achieves compile-time type safety through shared service definitions. It covers the architecture that enables both client and server to depend on a single source of truth for RPC method signatures, parameter types, and return types. This design eliminates a common class of runtime errors by ensuring that any mismatch between client and server results in a compile-time error rather than a runtime failure.

For implementation details about creating service definitions, see Creating Service Definitions. For details about method ID generation, see Method ID Generation. For serialization specifics, see Serialization with Bitcode.


The Type Safety Challenge in RPC Systems

Traditional RPC systems often face a fundamental challenge: ensuring that client and server agree on method signatures, parameter types, and return types. Common approaches include:

ApproachCompile-Time SafetyCross-PlatformExample
Schema filesPartial (code generation)YesgRPC with .proto files
Runtime validationNoYesJSON-RPC with runtime checks
Separate definitionsNoYesOpenAPI with client/server divergence
Shared type definitionsYesPlatform-dependentShared Rust code

Muxio uses the shared type definitions approach, but extends it to work across platforms (native and WASM) through its runtime-agnostic design. The core innovation is the RpcMethodPrebuffered trait, which defines a contract that both client and server implementations must satisfy.

Sources:


Shared Service Definitions Architecture

Analysis: The shared definition crate (example-muxio-rpc-service-definition) defines RPC methods by implementing the RpcMethodPrebuffered trait. Both server and client applications depend on this shared crate. The server uses decode_request() and encode_response() methods, while the client uses encode_request() and decode_response() (via the call() function). Because all implementations derive from the same trait implementation, the Rust compiler enforces type consistency at compile time.

Sources:


Compile-Time Type Safety Guarantees

Type Flow Diagram

Analysis: The type flow ensures that parameters pass through multiple type-checked boundaries. On the client side, the call() function requires parameters matching the Request associated type. The encode_request() method accepts this exact type. On the server side, decode_request() produces the same type, which the handler must accept. The response follows the reverse path with identical type checking. Any mismatch at any stage results in a compilation error, not a runtime error.

Sources:


The RpcMethodPrebuffered Trait Contract

Trait Structure

Analysis: Each RPC method implements the RpcMethodPrebuffered trait, defining its unique METHOD_ID, associated Request and Response types, and the required encoding/decoding methods. The trait enforces that all four encoding/decoding methods use consistent types. The call() method provides a high-level client interface that internally uses encode_request() and decode_response(), ensuring type safety throughout the call chain.

Sources:


Compile-Time Error Prevention

Type Mismatch Detection

The Rust compiler enforces type safety through multiple mechanisms:

Mismatch TypeDetection PointCompiler Error
Parameter type mismatchcall() invocationExpected Vec<f64>, found Vec<i32>
Handler input type mismatchdecode_request() callType mismatch in closure parameter
Handler output type mismatchencode_response() callExpected f64, found String
Response type mismatchcall() result bindingExpected f64, found i32
Method ID collisionTrait implementationDuplicate associated constant

Example: Type Mismatch Prevention

Analysis: The Rust type system prevents type mismatches before the code ever runs. When a developer attempts to call an RPC method with incorrect parameter types, the compiler immediately flags the error by comparing the provided type against the Request associated type defined in the shared trait implementation. This catch-early approach eliminates an entire class of integration bugs that would otherwise only surface at runtime, potentially in production.

Sources:


Cross-Platform Type Safety

Shared Definitions Across Client Types

Analysis: The shared definition crate enables identical type safety guarantees across all client platforms. Both RpcClient (native Tokio) and RpcWasmClient (WASM browser) depend on the same example-muxio-rpc-service-definition crate. Application code written for one client type can be ported to another client type with minimal changes, because both implement RpcServiceCallerInterface and both use the same call() methods with identical type signatures. The Rust compiler enforces that all platforms use matching types.

Sources:


Integration with Method IDs and Serialization

Type Safety Dependencies

Analysis: Type safety in muxio is achieved through the integration of three components. The RpcMethodPrebuffered trait defines the type contract. xxhash-rust generates unique METHOD_ID constants at compile time, enabling the compiler to detect method ID collisions. bitcode provides type-preserving binary serialization, ensuring that the types decoded on the server match the types encoded on the client. The Rust compiler verifies that all encoding, network transmission, and decoding operations preserve type integrity.

Sources:


Benefits Summary

The shared service definitions architecture provides several concrete benefits:

BenefitMechanismImpact
Compile-time error detectionRust type system enforces trait contractsBugs caught before runtime
API consistencySingle source of truth for method signaturesNo client-server divergence
Refactoring safetyType changes propagate to all dependentsCompiler guides migration
Cross-platform uniformitySame types work on native and WASMCode reuse across platforms
Zero runtime overheadAll checks happen at compile timeNo validation cost in production
Documentation through typesType signatures are self-documentingReduced documentation burden

Sources: