# mill-rpc
An RPC framework built on [`mill-io`](../mill-io) and [`mill-net`](../mill-net). Define services declaratively, get type-safe clients and servers — no async runtime required.
## Features
- **Zero async**: Handlers are plain synchronous functions
- **Macro-driven**: `mill_rpc::service!` generates a module with server trait, client struct, and dispatch logic
- **Selective generation**: Use `#[server]`, `#[client]`, or both (default)
- **Multi-service**: Host multiple services on a single port with automatic routing
- **Pluggable codecs**: Bincode by default, extensible
- **Binary wire protocol**: Efficient framing with one-way calls and ping/pong
## Quick Start
### Define a service
```rust
mill_rpc::service! {
service Calculator {
fn add(a: i32, b: i32) -> i32;
fn multiply(a: i64, b: i64) -> i64;
}
}
```
This generates a `calculator` module containing:
- `calculator::Service`: trait to implement on the server
- `calculator::server(impl)`: wraps your impl for registration
- `calculator::Client`: struct with typed RPC methods
- `calculator::methods`: method ID constants
### Server
```rust
struct MyCalc;
impl calculator::Service for MyCalc {
fn add(&self, _ctx: &RpcContext, a: i32, b: i32) -> i32 { a + b }
fn multiply(&self, _ctx: &RpcContext, a: i64, b: i64) -> i64 { a * b }
}
fn main() {
let event_loop = Arc::new(EventLoop::new(4, 1024, 100).unwrap());
let _server = RpcServer::builder()
.bind("127.0.0.1:9001".parse().unwrap())
.service(calculator::server(MyCalc))
.build(&event_loop)
.unwrap();
event_loop.run().unwrap();
}
```
### Client
```rust
let transport = RpcClient::connect(addr, &event_loop).unwrap();
let client = calculator::Client::new(transport, Codec::bincode(), 0);
let sum = client.add(10, 25).unwrap(); // 35
let prod = client.multiply(7, 8).unwrap(); // 56
```
## Selective Generation
Generate only what you need:
```rust
// Server crate: no client code generated
mill_rpc::service! {
#[server]
service Calculator {
fn add(a: i32, b: i32) -> i32;
}
}
// Client crate: no server code generated
mill_rpc::service! {
#[client]
service Calculator {
fn add(a: i32, b: i32) -> i32;
}
}
// Both (default): for tests, examples, or single-binary apps
mill_rpc::service! {
service Calculator {
fn add(a: i32, b: i32) -> i32;
}
}
```
## Multi-Service Server
```rust
mill_rpc::service! {
#[server]
service MathService {
fn factorial(n: u64) -> u64;
}
}
mill_rpc::service! {
#[server]
service StringService {
fn reverse(s: String) -> String;
}
}
let _server = RpcServer::builder()
.bind(addr)
.service(math_service::server(MathImpl)) // service_id = 0
.service(string_service::server(StringImpl)) // service_id = 1
.build(&event_loop)?;
// Client side: share one connection
let math = math_service::Client::new(transport.clone(), codec, 0);
let strings = string_service::Client::new(transport, codec, 1);
```
## Examples
```bash
# Terminal 1 # Terminal 2
cargo run --example calculator_server cargo run --example calculator_client
cargo run --example echo_server cargo run --example echo_client
cargo run --example kv_server cargo run --example kv_client
cargo run --example multi_service_server cargo run --example multi_service_client
# Self-contained stress test
cargo run --example concurrent_clients
```
## License
Licensed under the Apache License, Version 2.0. See [LICENSE](../LICENSE) for details.