A modern, high-performance RPC framework for Rust inspired by ZeroC Ice, featuring complete Ice API compatibility, bidirectional RPC, connection multiplexing, type-safe service definitions via #[ice_service] macro, and 100% async/await API.
- ✅ ZeroC Ice API Compatible - camelCase & snake_case dual API, seamless migration
- ✅ Bidirectional RPC - Server-to-client callbacks over existing connections (firewall-friendly)
- ✅ Router Support - Glacier2-compatible routing with
createObjectAdapterWithRouter - ✅ Zero Boilerplate - One macro generates everything: trait, client, server, servant
- ✅ 100% Async - Built on Tokio, all operations are non-blocking
- ✅ Type-Safe - Compile-time checking, no runtime reflection
- ✅ Connection Multiplexing - Up to 10,000 streams per TCP connection
- ✅ Ice-Inspired Architecture - Proxy-Servant pattern, Communicator, ObjectAdapter
- ✅ Multi-Protocol Support - TCP, UDP, SSL, WebSocket, HTTP/2, HTTP/3, IPC
- ✅ Native Rust - Uses Rust traits and proc-macros, no external IDL compiler
use ice0::prelude::*;
// Define service with one macro
#[ice_service]
pub trait Greeter {
async fn greet(name: String) -> String;
async fn add(a: i32, b: i32) -> i32;
}The #[ice_service] macro automatically generates:
Greetertrait - Service interface with context parameterGreeterClient- Client proxy for RPC callsGreeterServer- Request dispatcherGreeterServant- Ice framework integration ⭐
pub struct GreeterImpl;
#[async_trait::async_trait]
impl Greeter for GreeterImpl {
async fn greet(&self, _context: context::Context, name: String) -> String {
format!("Hello, {}! Welcome to Ice RPC!", name)
}
async fn add(&self, _context: context::Context, a: i32, b: i32) -> i32 {
a + b
}
}#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create communicator
let comm = Communicator::new().await?;
// Create adapter with endpoint
let adapter = comm.create_object_adapter("GreeterAdapter", vec![
Endpoint::from_string("tcp -h 127.0.0.1 -p 9529")?
]).await?;
// ✅ One line to create servant (auto-generated by macro!)
let servant = GreeterServant::new(GreeterImpl);
// Register servant
adapter.add_servant(
Identity::new("Greeter"), // Must match trait name
Arc::new(servant),
).await;
// Activate adapter
adapter.activate().await;
comm.activate_adapter(&adapter).await;
info!("Greeter service listening on port 9529");
// Wait for shutdown
tokio::signal::ctrl_c().await?;
comm.shutdown().await?;
Ok(())
}#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create communicator
let comm = Communicator::new().await?;
// Create proxy (Ice's stringToProxy pattern)
let proxy = comm
.string_to_proxy("Greeter:tcp -h 127.0.0.1 -p 9529")
.await?;
// Get multiplexer and create client
let mux = proxy.mux().unwrap().clone();
let client = GreeterClient::new(mux, 0);
// Call RPC methods - type-safe!
let greeting = client.greet("Ice RPC".to_string()).await?;
println!("{}", greeting); // "Hello, Ice RPC! Welcome to Ice RPC!"
let sum = client.add(42, 58).await?;
println!("42 + 58 = {}", sum); // 100
Ok(())
}That's it! No manual serialization, no message handling, no dispatch logic. The macro generates everything!
Our framework supports Ice-style bidirectional connections where the server can callback the client over the existing client-initiated connection:
// 1. Create callback adapter (no endpoints)
let callback_adapter = comm
.createObjectAdapter("ClientCallback") // Ice style!
.await?;
// 2. Register callback servant
adapter.add(
Identity::new("ClientEventCallback"),
Arc::new(ClientCallbackServant)
).await;
// 3. Activate adapter
adapter.activate().await;
// 4. Get connection and bind adapter
let connection = proxy.ice_getConnection().unwrap(); // Ice API!
connection.setAdapter(callback_adapter); // Ice API!// In your servant's dispatch method:
async fn dispatch(&self, identity, operation, request, context) {
// Get callback proxy from context
if let Some(callback_proxy) = context.callback_proxy(callback_identity) {
// Callback to client over existing connection!
callback_proxy.invoke("onEvent", event_data).await?;
}
}- ✅ Firewall-Friendly - Reuses client-initiated connection
- ✅ Ice API Compatible -
ice_getConnection(),setAdapter(),createProxy() - ✅ Full ObjectAdapter - Uses complete Ice adapter infrastructure
- ✅ Type-Safe - Compile-time servant dispatch
- ✅ No Extra Ports - No need for server to connect back to client
When receiving callbacks on the client side, you must dispatch through ObjectAdapter, not directly to Multiplexer:
// ✅ CORRECT - Dispatch through ObjectAdapter
match callback_adapter.dispatch(identity, operation, data, context).await {
Ok(response) => {
let response_msg = Message::response(request_id, 0, response);
mux.send_message(response_msg).await?;
}
Err(e) => {
let error_msg = Message::error(request_id, 0, e.to_string());
mux.send_message(error_msg).await?;
}
}
// ❌ WRONG - Multiplexer doesn't have registered servants
mux.dispatch_request(&identity, &operation, data, request_id).await?;See examples/bidirectional_client.rs and examples/bidirectional_server.rs for complete examples.
✅ Type-Safe Service Definition - #[ice_service] macro generates all code
✅ Auto-Generated Servant - No boilerplate, just implement the trait
✅ Proxy-Servant Architecture - Clean separation of client/server
✅ 100% Async/Await - Built on Tokio, zero blocking
✅ Connection Multiplexing - Multiple logical streams over one TCP connection
✅ Multi-Protocol Transports - TCP, UDP, SSL, WebSocket, HTTP/2, HTTP/3, IPC
✅ Ice-Style Identity - Object references with string format
✅ Deadline Propagation - Automatic timeout handling
✅ Request Context - Metadata propagation
✅ Error Handling - Typed error types with thiserror
✅ Dual API Styles - camelCase (Ice) and snake_case (Rust)
Our framework provides complete Ice API compatibility with both camelCase and snake_case styles:
// Ice style (camelCase)
let proxy = comm.stringToProxy("Greeter:tcp -h 127.0.0.1 -p 9630").await?;
let adapter = comm.createObjectAdapter("MyAdapter").await?;
let adapter = comm.createObjectAdapterWithEndpoints("MyAdapter", "tcp -h 127.0.0.1 -p 9630").await?;
let router_adapter = comm.createObjectAdapterWithRouter("CallbackAdapter", Some(router_proxy)).await?;
// Rust style (snake_case)
let proxy = comm.string_to_proxy("Greeter:tcp -h 127.0.0.1 -p 9630").await?;
let adapter = comm.create_object_adapter("MyAdapter", vec![]).await?;// Set default router (all new proxies will use it)
let router = comm.string_to_proxy("Glacier2/router:tcp -h router:4063").await?;
comm.setDefaultRouter(Some(router)).await;
comm.set_default_router(Some(router)).await; // Rust style
// Get default router
let router = comm.getDefaultRouter().await;
let router = comm.get_default_router().await; // Rust style
// Create adapter with router (for receiving callbacks)
let callback_adapter = comm.createObjectAdapterWithRouter(
"ClientCallbackAdapter",
Some(router_proxy)
).await?;
// Check if adapter has router
if adapter.has_router().await {
let router = adapter.get_router().await;
}// Configure router for specific proxy
let proxy = proxy.ice_router(Some(router));
let router = proxy.ice_getRouter();// Get connection for bidirectional RPC
let conn = proxy.ice_getConnection().unwrap();
let conn = proxy.ice_getCachedConnection().unwrap();
// Configure proxy
let proxy = proxy.ice_oneway();
let proxy = proxy.ice_twoway();
let proxy = proxy.ice_timeout(30000); // 30 seconds
let proxy = proxy.ice_isFixed();// Bidirectional RPC setup
conn.setAdapter(callback_adapter);
let adapter = conn.getAdapter();
let callback_proxy = conn.createProxy(callback_identity);
// Connection management
conn.close().await?;
let active = conn.is_active();
let type_ = conn.type_();// Lifecycle
adapter.activate().await;
adapter.hold().await;
adapter.deactivate().await;
adapter.waitForHold().await;
// Servant management
adapter.add(identity, servant).await;
adapter.addDefaultServant(servant, Some("category")).await;
adapter.remove_servant(&identity).await;
// Information
let name = adapter.get_name();See ICE_API_COMPATIBILITY.md for complete API reference.
- Twoway - Request-response with response future
- Oneway - Fire-and-forget
- Datagram - Best-effort over UDP
┌──────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────────────────────────────┐ │
│ │ #[ice_service] Generated Code │ │
│ │ - Trait, Client, Server, Servant │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
▲
│
┌──────────────────┴──────────────────────────┐
│ Ice Framework Layer │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │Communicat│ │Object │ │Bidirect- │ │
│ │or │ │Adapter │ │ional RPC │ │
│ └──────────┘ └──────────┘ └───────────┘ │
└──────────────────────────────────────────────┘
▲
│
┌──────────────────┴──────────────────────────┐
│ Multiplexing & Transport │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ Multiplexer │ │ TCP/UDP/SSL/ │ │
│ │ (Streams) │ │ WS/HTTP2/3/IPC │ │
│ └──────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────┘
Central entry point for the RPC system:
let comm = Communicator::new().await?;
// Create server adapter
let adapter = comm.create_object_adapter("MyAdapter", vec![
Endpoint::from_string("tcp -h 127.0.0.1 -p 9529")?
]).await?;
// Create client proxy
let proxy = comm.string_to_proxy(
"ServiceName:tcp -h 127.0.0.1 -p 9529"
).await?;Manages servants and listens for connections:
adapter.add_servant(
Identity::new("Greeter"),
Arc::new(GreeterServant::new(GreeterImpl)),
).await;
adapter.activate().await;
comm.activate_adapter(&adapter).await;Ice-style object identification:
// Simple identity
let identity = Identity::new("Greeter");
// Identity with category
let identity = Identity::with_category("Greeter", "v1");
// String conversion
let proxy_string = "Greeter:tcp -h 127.0.0.1 -p 9529";Important: Identity name must match the trait name (case-sensitive)!
The #[ice_service] macro is the heart of the framework:
#[ice_service]
pub trait MyService {
async fn method1(arg1: String, arg2: i32) -> String;
async fn method2(x: f64) -> Vec<u8>;
}Generates:
// Trait
#[async_trait]
pub trait MyService: Send + Sync + 'static {
async fn method1(&self, ctx: Context, arg1: String, arg2: i32) -> String;
async fn method2(&self, ctx: Context, x: f64) -> Vec<u8>;
}
// Client
pub struct MyServiceClient { ... }
impl MyServiceClient {
pub async fn method1(&self, arg1: String, arg2: i32) -> ice0::Result<String>;
pub async fn method2(&self, x: f64) -> ice0::Result<Vec<u8>>;
}
// Server
pub struct MyServiceServer<S: MyService> { ... }
impl<S: MyService> MyServiceServer<S> {
pub async fn handle_request(&self, ctx: Context, msg: Message)
-> ice0::Result<Message>;
}
// Servant (auto-generated!)
pub struct MyServiceServant<S: MyService> { ... }
impl<S: MyService + 'static> ice0::adapter::Servant for MyServiceServant<S> {
async fn dispatch(&self, ...) -> Result<Vec<u8>, DispatchError>;
}Request metadata and deadline propagation:
pub struct Context {
pub deadline: Option<Duration>,
pub trace_id: Option<String>,
}
// Create with deadline
let ctx = Context::new()
.with_deadline(Duration::from_secs(5));Terminal 1 - Server:
cargo run --bin ice_service_serverTerminal 2 - Client:
cargo run --bin ice_service_clientExpected Output:
greet() response: Hello, Ice RPC! Welcome to Ice RPC!
add(42, 58) = 100
Terminal 1 - Server:
cargo run --bin bidirectional_serverTerminal 2 - Client:
cargo run --bin bidirectional_clientExpected Output:
[Client Callback] 📨 Event: Hello from server! Greeting: Ice RPC Framework
[Client Callback] 📊 Progress: 100%
# Router example (Glacier2-compatible routing)
cargo run --bin router_example
# Legacy async API (without macro)
cargo run --bin ice_async_server
cargo run --bin ice_async_client
# Ice-style bidirectional
cargo run --bin ice_style_bidirectional
# Raw TCP example
cargo run --bin tcp_server
cargo run --bin tcp_client
# UDP example
cargo run --bin udp_server
cargo run --bin udp_client
# HTTP/2 example
cargo run --bin http2_server
cargo run --bin http2_client
# WebSocket example
cargo run --bin ws_server
cargo run --bin ws_clientice/
├── ice/ # Main crate (merged ice + ice-core)
│ └── src/
│ ├── adapter.rs # ObjectAdapter & Servant management
│ ├── communicator.rs # Communicator (central entry point)
│ ├── connection.rs # Connection abstraction (bidirectional RPC)
│ ├── identity.rs # Identity & ObjectReference
│ ├── message.rs # Message types and protocol
│ ├── mux.rs # Connection multiplexing
│ ├── proxy.rs # Proxy (AMI client, Ice API)
│ ├── endpoints/ # Protocol implementations
│ │ ├── tcp.rs # TCP transport
│ │ ├── udp.rs # UDP transport
│ │ ├── ssl.rs # SSL/TLS transport
│ │ ├── ws.rs # WebSocket transport
│ │ ├── http2.rs # HTTP/2 transport
│ │ ├── http3.rs # HTTP/3 (QUIC) transport
│ │ └── ipc.rs # IPC (Unix/Windows) transport
│ ├── error.rs # Error types
│ ├── lib.rs # Re-exports and prelude
│ └── prelude.rs # Common imports
│
├── ice0-macro/ # #[ice_service] proc-macro
│ └── src/
│ └── lib.rs # Code generation for trait, client, server, servant
│
├── examples/
│ ├── ice_service_server.rs # ⭐ Macro-based server
│ ├── ice_service_client.rs # ⭐ Macro-based client
│ ├── bidirectional_server.rs # ⭐ Bidirectional RPC server
│ ├── bidirectional_client.rs # ⭐ Bidirectional RPC client
│ ├── router_example.rs # ⭐ Router/Glacier2 routing
│ ├── ice_style_bidirectional.rs # Ice-style bidirectional
│ ├── ice_async_server.rs # Legacy API server
│ ├── ice_async_client.rs # Legacy API client
│ ├── tcp_server.rs # Raw TCP example
│ ├── tcp_client.rs # Raw TCP example
│ ├── udp_server.rs # Raw UDP example
│ └── udp_client.rs # Raw UDP example
│
├── README.md # 本文件(中文)
├── README.EN.md # 本文件(英文)
├── ARCHITECTURE.md # 架构指南(中文)
├── ARCHITECTURE.EN.md # 架构指南(英文)
└── ICE_API_COMPATIBILITY.md # Ice API 参考
# Build all
cargo build
# Run tests
cargo test
# Run specific example
cargo run --bin ice_service_server
cargo run --bin ice_service_client| Feature | ZeroC Ice | Ice RPC (This Framework) |
|---|---|---|
| Language | Multi-language (C++, Java, Python, etc.) | Rust only |
| Code Generation | External IDL compiler (slice2cpp, etc.) | Rust proc-macro (zero config) |
| IDL Language | Slice (custom language) | Rust traits (native syntax) |
| API Style | Synchronous + Async options | 100% Async/Await |
| Runtime | Custom runtime | Tokio |
| Servant Pattern | Abstract base class (inheritance) | Trait implementation (composition) |
| Multiplexing | Connection pooling | Native stream multiplexing |
| Type Safety | Runtime checks | Compile-time checks |
| Boilerplate | Manual servant implementation | Auto-generated servant |
| Build Steps | Compile .ice files first | Just cargo build |
| Ice API | Native | Fully compatible (camelCase + snake_case) |
| Bidirectional RPC | Yes (setAdapter) | Yes (complete Ice pattern) |
| Protocols | TCP, SSL, UDP, WS | TCP, SSL, UDP, WS, HTTP/2, HTTP/3, IPC |
- ✅ No external compiler - Just
cargo build - ✅ Native Rust syntax - No Slice language to learn
- ✅ Zero boilerplate - One macro generates everything
- ✅ Better type safety - Compile-time vs runtime checks
- ✅ Modern async - Built for async/await from the ground up
- ✅ Simpler API - No protobuf files, just Rust traits
- ✅ Ice architecture - Proxy-Servant pattern is more flexible
- ✅ Built-in multiplexing - No HTTP/2 complexity
- ✅ Multiple transports - TCP, UDP (gRPC is HTTP-only)
- ✅ More features - Servant locators, identity system, deadlines
- ✅ Better multiplexing - Stream-based, not connection-per-call
- ✅ Ice-inspired - Proven architecture from ZeroC
Uses bincode for efficient binary serialization:
// Automatic in generated code
let args_bytes = bincode::serialize(&args)?;
let result: ReturnType = bincode::deserialize(&response_bytes)?;Custom binary protocol with frame-based encoding:
┌──────────┬─────────┬──────────┬───────────┐
│ Magic │ Version │ MsgType │ StreamID │
│ (4B) │ (2B) │ (1B) │ (8B) │
├──────────┼─────────┼──────────┼───────────┤
│ MessageID│ Service │ Method │ Deadline │
│ (8B) │ (var) │ (var) │ (opt) │
├──────────┼─────────┴──────────┴───────────┤
│ Content Length (4B) │
├───────────────────────────────────────────┤
│ Payload (variable length) │
└───────────────────────────────────────────┘
Each TCP connection supports multiple logical streams:
- Stream ID: 64-bit identifier
- Message ID: 64-bit per-stream sequence
- Max Streams: 10,000 per connection
- Request-Response Correlation: Oneshot channels
- Trait name: PascalCase (e.g.,
Greeter,UserService) - Identity: Must match trait name exactly (case-sensitive!)
- Methods: snake_case (e.g.,
get_user,send_message)
#[ice_service]
pub trait UserService { // ← PascalCase
async fn get_user(id: i32) -> User;
}
// Server
adapter.add_servant(
Identity::new("UserService"), // ← Must match exactly!
Arc::new(servant),
).await;// Client side
match client.greet(name).await {
Ok(response) => println!("{}", response),
Err(ice0::error::Error::Timeout) => eprintln!("Request timed out"),
Err(ice0::error::Error::NotFound(msg)) => eprintln!("Method not found: {}", msg),
Err(ice0::error::Error::Serialization(e)) => eprintln!("Serialization error: {}", e),
Err(e) => eprintln!("Error: {}", e),
}// Client creates context with deadline
let ctx = context::Context::new()
.with_deadline(Duration::from_secs(5));
// Server receives context
async fn greet(&self, context: Context, name: String) -> String {
if let Some(deadline) = context.deadline {
info!("Request deadline: {:?}", deadline);
}
format!("Hello, {}!", name)
}// Multiple concurrent RPCs
let mut handles = Vec::new();
for i in 1..=5 {
let client = client.clone();
let handle = tokio::spawn(async move {
let name = format!("User{}", i);
client.greet(name).await
});
handles.push(handle);
}
// Wait for all
for handle in handles {
let result = handle.await.unwrap()?;
println!("{}", result);
}// Client: Dispatch callbacks through ObjectAdapter (NOT Multiplexer)
match adapter.dispatch(identity, op, data, context).await {
Ok(response) => mux.send_message(Message::response(id, 0, response)).await?,
Err(e) => mux.send_message(Message::error(id, 0, e.to_string())).await?,
}
// Server: Handle Response messages in receive loop
if message.header.message_type == MessageType::Response {
mux.route_response(message); // Route to waiting future
}- TLS/SSL transport support
- WebSocket transport
- HTTP/2 transport
- HTTP/3 (QUIC) transport
- IPC transport (Unix domain sockets + Windows named pipes)
- Complete Ice API compatibility (camelCase + snake_case)
- Bidirectional RPC (server-to-client callbacks)
- Ice-style connection pattern (ice_getConnection, setAdapter, createProxy)
- Response routing for bidirectional callbacks (route_response)
- Router support (createObjectAdapterWithRouter, setDefaultRouter, Glacier2-compatible)
- Compression (gzip, zstd)
- Load balancing
- Service discovery
- Metrics export (Prometheus)
- Retry policies
- Circuit breaker pattern
- Streaming RPC (bidirectional streams)
- gRPC interoperability layer
- IceGrid router support
- Collocation optimization
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- ZeroC Ice - For the excellent Proxy-Servant architecture inspiration
- Tokio - For the async runtime
- gRPC - For modern RPC design patterns
- tarpc - For Rust-native RPC concepts