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
Relevant source files
- README.md
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
Purpose and Scope
This document explains how rust-muxio achieves compile-time type safety for RPC operations through shared service definitions. It covers the RpcMethodPrebuffered trait, how method definitions are shared between clients and servers, and the compile-time guarantees that eliminate entire classes of distributed system bugs.
For step-by-step instructions on implementing custom service definitions, see Creating Service Definitions. For details on METHOD_ID generation and collision prevention, see Method ID Generation. For serialization implementation details, see Serialization with Bitcode.
Architecture Overview
The type safety model in rust-muxio is built on a simple principle: both client and server implementations depend on the same service definition crate. This shared dependency ensures that any incompatibility between client expectations and server behavior results in a compile-time error rather than a runtime failure.
graph TB
subgraph ServiceDef["Service Definition Crate\n(example-muxio-rpc-service-definition)"]
Trait["RpcMethodPrebuffered trait"]
Add["Add struct\nMETHOD_ID: u64"]
Mult["Mult struct\nMETHOD_ID: u64"]
Echo["Echo struct\nMETHOD_ID: u64"]
end
subgraph Client["Client Implementations"]
TokioClient["muxio-tokio-rpc-client"]
WasmClient["muxio-wasm-rpc-client"]
ClientCode["Application Code:\nAdd::call(client, params)"]
end
subgraph Server["Server Implementation"]
TokioServer["muxio-tokio-rpc-server"]
ServerCode["Handler Registration:\nendpoint.register_prebuffered(Add::METHOD_ID, handler)"]
end
Trait --> Add
Trait --> Mult
Trait --> Echo
Add -.depends on.-> ClientCode
Add -.depends on.-> ServerCode
ClientCode --> TokioClient
ClientCode --> WasmClient
ServerCode --> TokioServer
Add -.enforces contract.-> ClientCode
Add -.enforces contract.-> ServerCode
Shared Definition Pattern
Sources:
- README.md49
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-14
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:21-37
The RpcMethodPrebuffered Trait
The [RpcMethodPrebuffered trait](https://github.com/jzombie/rust-muxio/blob/fcb45826/RpcMethodPrebuffered trait) defines the contract for a complete request/response RPC method. Each method implementation provides:
| Trait Element | Type | Purpose |
|---|---|---|
METHOD_ID | u64 | Compile-time unique identifier (xxhash of method name) |
Input | Associated Type | Request parameter type |
Output | Associated Type | Response result type |
encode_request() | Method | Serialize Input to bytes |
decode_request() | Method | Deserialize bytes to Input |
encode_response() | Method | Serialize Output to bytes |
decode_response() | Method | Deserialize bytes to Output |
Type Flow Through System Layers
Sources:
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-21
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-42
Compile-Time Guarantees
Type Safety Enforcement
The shared service definition pattern enforces three critical guarantees at compile time:
- Parameter Type Matching : The client's call site must pass arguments that match the
Inputassociated type - Response Type Matching : The handler must return a value matching the
Outputassociated type - METHOD_ID Uniqueness : Duplicate method names result in compile-time constant collision
Example: Type Mismatch Detection
The following table illustrates how type mismatches are caught:
| Scenario | Client Code | Server Code | Result |
|---|---|---|---|
| Correct | Add::call(client, vec![1.0, 2.0]) | Handler returns f64 | ✓ Compiles |
| Wrong Input Type | Add::call(client, "invalid") | Handler expects Vec<f64> | ✗ Compile error: type mismatch |
| Wrong Output Type | Client expects f64 | Handler returns String | ✗ Compile error: trait bound not satisfied |
| Missing Handler | Add::call(client, params) | No handler registered | ✓ Compiles, runtime NotFound error |
Code Entity Mapping
Sources:
- README.md:69-117
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-95
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:226-239
Client-Server Contract Enforcement
RpcCallPrebuffered Implementation
The [RpcCallPrebuffered trait](https://github.com/jzombie/rust-muxio/blob/fcb45826/RpcCallPrebuffered trait) provides the client-side call interface. It is automatically implemented for all types that implement RpcMethodPrebuffered:
This design ensures that:
- The
call()method always receivesSelf::Input(enforced by trait bounds) - The return type is always
Result<Self::Output, _>(enforced by trait signature) - Both encoding and decoding use the shared implementation from
RpcMethodPrebuffered
Sources:
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-29
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:49-98
Cross-Platform Type Safety
Sources:
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-88
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:126-133
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-60
Integration Test Validation
The integration tests demonstrate identical usage patterns across both Tokio and WASM clients, validating the shared definition model:
Tokio Client Test Pattern
1. Server registers handler: endpoint.register_prebuffered(Add::METHOD_ID, handler)
2. Handler decodes: Add::decode_request(&request_bytes)
3. Handler encodes: Add::encode_response(sum)
4. Client calls: Add::call(&client, vec![1.0, 2.0, 3.0])
5. Assertion: assert_eq!(result, 6.0)
WASM Client Test Pattern
1. Server registers handler: endpoint.register_prebuffered(Add::METHOD_ID, handler)
2. Handler decodes: Add::decode_request(&request_bytes)
3. Handler encodes: Add::encode_response(sum)
4. Client calls: Add::call(&client, vec![1.0, 2.0, 3.0])
5. Assertion: assert_eq!(result, 6.0)
The patterns are identical except for the client type. This demonstrates that:
- Application logic is transport-agnostic
- Service definitions work identically across platforms
- Type safety is maintained regardless of transport implementation
Sources:
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:18-97
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:40-142
Error Handling and Type Safety
Runtime Error Detection
While type mismatches are caught at compile time, certain errors can only be detected at runtime:
| Error Type | Detection Time | Example |
|---|---|---|
| Type mismatch | Compile time | Add::call(client, "wrong type") → Compile error |
| Missing field in struct | Compile time | Client uses Add v2, server has Add v1 → Compile error if both recompile |
| Method not registered | Runtime | Client calls Add, server never registered handler → RpcServiceError::NotFound |
| Handler logic error | Runtime | Handler returns Err("Addition failed") → RpcServiceError::Rpc |
Runtime Error Propagation
Sources:
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:99-152
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:205-240
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:144-227
Large Payload Handling
The RpcCallPrebuffered implementation includes automatic handling for large payloads. The type system ensures that this complexity is transparent to application code:
| Payload Size | Transport Strategy | Type Safety Impact |
|---|---|---|
| < 64KB | Sent in rpc_param_bytes header field | None - same types |
| ≥ 64KB | Sent in rpc_prebuffered_payload_bytes, chunked automatically | None - same types |
The decision is made based on the serialized byte length, not the Rust type. The following code from extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:58-65 shows this logic:
Application code calling Add::call(client, vec![1.0; 1_000_000]) receives the same type safety guarantees as Add::call(client, vec![1.0; 3]).
Sources:
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:30-65
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:154-203
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:229-312
Summary
The type safety model in rust-muxio provides the following guarantees:
- Compile-Time Contract Enforcement : Both client and server depend on the same service definition crate, ensuring API compatibility
- Type-Safe Method Calls : The
RpcCallPrebufferedtrait ensures thatInputandOutputtypes are correctly used at all call sites - Transport Agnosticism : The same type-safe definitions work identically across Tokio and WASM clients
- Automatic Serialization : Encoding and decoding are encapsulated in the method definition, hidden from application code
- Early Error Detection : Type mismatches, missing fields, and incompatible changes result in compile errors, not runtime failures
This design eliminates an entire class of distributed system bugs where client and server implementations drift apart over time. Any breaking change to a service definition requires both client and server code to be updated simultaneously, and the compiler enforces this constraint.
Sources:
- README.md49
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-241
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-313
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:1-99
Dismiss
Refresh this wiki
Enter email to refresh