<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<meta charset="UTF-8">
<title>API Reference - RpcNet Guide</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<script>
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex.js";
</script>
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">RpcNet Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="api-reference"><a class="header" href="#api-reference">API Reference</a></h1>
<p>Quick reference for RpcNet's most commonly used APIs. For complete documentation, see the <a href="https://docs.rs/rpcnet">API docs</a>.</p>
<h2 id="core-types"><a class="header" href="#core-types">Core Types</a></h2>
<h3 id="server"><a class="header" href="#server">Server</a></h3>
<p>Creates and manages RPC servers.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::{Server, ServerConfig};
// Create server
let config = ServerConfig::builder()
.with_cert_and_key(cert, key)?
.build();
let mut server = Server::new(config);
// Register services
server.register_service(Arc::new(MyService));
// Bind and run
server.bind("0.0.0.0:8080").await?;
server.run().await?;
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>new(config)</code> - Create server with configuration</li>
<li><code>register_service(service)</code> - Register RPC service handler</li>
<li><code>bind(addr)</code> - Bind to address</li>
<li><code>enable_cluster(config)</code> - Enable cluster features</li>
<li><code>run()</code> - Start server (blocks until shutdown)</li>
<li><code>shutdown()</code> - Gracefully shut down server</li>
</ul>
<h3 id="client"><a class="header" href="#client">Client</a></h3>
<p>Connects to RPC servers and makes requests.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::{Client, ClientConfig};
// Create client
let config = ClientConfig::builder()
.with_server_cert(cert)?
.build();
// Connect
let client = MyServiceClient::connect("server.example.com:8080", config).await?;
// Make request
let response = client.my_method(args).await?;
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>connect(addr, config)</code> - Connect to server</li>
<li>Generated methods per RPC trait</li>
<li>Auto-reconnect on connection loss</li>
</ul>
<h2 id="cluster-apis"><a class="header" href="#cluster-apis">Cluster APIs</a></h2>
<h3 id="clustermembership"><a class="header" href="#clustermembership">ClusterMembership</a></h3>
<p>Manages node membership via SWIM gossip protocol.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::ClusterMembership;
// Create cluster
let config = ClusterConfig::default()
.with_bind_addr("0.0.0.0:7946".parse()?);
let cluster = ClusterMembership::new(config).await?;
// Join via seed nodes
cluster.join(vec!["seed.example.com:7946".parse()?]).await?;
// Tag node
cluster.set_tag("role", "worker");
// Subscribe to events
let mut events = cluster.subscribe();
while let Some(event) = events.recv().await {
// Handle cluster events
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>new(config)</code> - Create cluster membership</li>
<li><code>join(seeds)</code> - Join cluster via seed nodes</li>
<li><code>leave()</code> - Gracefully leave cluster</li>
<li><code>set_tag(key, value)</code> - Set metadata tag</li>
<li><code>get_tag(key)</code> - Get metadata tag</li>
<li><code>nodes()</code> - Get all cluster nodes</li>
<li><code>subscribe()</code> - Subscribe to cluster events</li>
<li><code>local_node_id()</code> - Get local node ID</li>
</ul>
<h3 id="workerregistry"><a class="header" href="#workerregistry">WorkerRegistry</a></h3>
<p>Tracks worker nodes with load balancing.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::{WorkerRegistry, LoadBalancingStrategy};
// Create registry
let registry = Arc::new(WorkerRegistry::new(
cluster,
LoadBalancingStrategy::LeastConnections
));
// Start monitoring
registry.start().await;
// Select worker
let worker = registry.select_worker(Some("role=worker")).await?;
println!("Selected: {} at {}", worker.label, worker.addr);
// Get all workers
let workers = registry.workers().await;
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>new(cluster, strategy)</code> - Create registry</li>
<li><code>start()</code> - Start monitoring cluster events</li>
<li><code>select_worker(filter)</code> - Select worker by tag filter</li>
<li><code>workers()</code> - Get all workers</li>
<li><code>worker_count()</code> - Get number of workers</li>
<li><code>subscribe()</code> - Subscribe to registry events</li>
</ul>
<h3 id="noderegistry"><a class="header" href="#noderegistry">NodeRegistry</a></h3>
<p>Tracks all cluster nodes.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::NodeRegistry;
// Create registry
let registry = Arc::new(NodeRegistry::new(cluster));
registry.start().await;
// Get all nodes
let nodes = registry.nodes().await;
// Filter by tag
let directors = nodes.iter()
.filter(|n| n.tags.get("role") == Some(&"director".to_string()))
.collect::<Vec<_>>();
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>new(cluster)</code> - Create node registry</li>
<li><code>start()</code> - Start monitoring cluster</li>
<li><code>nodes()</code> - Get all nodes</li>
<li><code>node_count()</code> - Count nodes</li>
<li><code>subscribe()</code> - Subscribe to events</li>
</ul>
<h3 id="clusterclient"><a class="header" href="#clusterclient">ClusterClient</a></h3>
<p>High-level API for calling workers.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::{ClusterClient, ClusterClientConfig};
// Create client
let config = ClusterClientConfig::default();
let client = Arc::new(ClusterClient::new(registry, config));
// Call any worker
let result = client.call_worker("compute", request, Some("role=worker")).await?;
<span class="boring">}</span></code></pre></pre>
<p><strong>Key methods</strong>:</p>
<ul>
<li><code>new(registry, config)</code> - Create cluster client</li>
<li><code>call_worker(method, data, filter)</code> - Call any worker matching filter</li>
</ul>
<h2 id="configuration"><a class="header" href="#configuration">Configuration</a></h2>
<h3 id="serverconfig"><a class="header" href="#serverconfig">ServerConfig</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::ServerConfig;
let config = ServerConfig::builder()
.with_cert_and_key(cert, key)? // TLS certificate and key
.with_ca_cert(ca)? // CA certificate for client verification
.with_max_concurrent_streams(100)? // Max concurrent QUIC streams
.with_max_idle_timeout(Duration::from_secs(30))? // Idle timeout
.build();
<span class="boring">}</span></code></pre></pre>
<h3 id="clientconfig"><a class="header" href="#clientconfig">ClientConfig</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::ClientConfig;
let config = ClientConfig::builder()
.with_server_cert(cert)? // Server certificate
.with_ca_cert(ca)? // CA certificate
.with_connect_timeout(Duration::from_secs(5))? // Connection timeout
.build();
<span class="boring">}</span></code></pre></pre>
<h3 id="clusterconfig"><a class="header" href="#clusterconfig">ClusterConfig</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::ClusterConfig;
let config = ClusterConfig::default()
.with_bind_addr("0.0.0.0:7946".parse()?)
.with_gossip_interval(Duration::from_secs(1))
.with_health_check_interval(Duration::from_secs(2))
.with_phi_threshold(8.0);
<span class="boring">}</span></code></pre></pre>
<h2 id="code-generation"><a class="header" href="#code-generation">Code Generation</a></h2>
<h3 id="rpc-trait-definition"><a class="header" href="#rpc-trait-definition">RPC Trait Definition</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::prelude::*;
#[rpc_trait]
pub trait MyService {
async fn my_method(&self, arg1: String, arg2: i32) -> Result<Response>;
async fn streaming(&self, request: Request) -> impl Stream<Item = Result<Chunk>>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response {
pub data: Vec<u8>,
}
<span class="boring">}</span></code></pre></pre>
<h3 id="generate-code"><a class="header" href="#generate-code">Generate Code</a></h3>
<pre><code class="language-bash">rpcnet-gen --input my_service.rpc.rs --output src/generated
</code></pre>
<h3 id="use-generated-code"><a class="header" href="#use-generated-code">Use Generated Code</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>mod generated;
use generated::my_service::*;
// Server side
#[rpc_impl]
impl MyService for Handler {
async fn my_method(&self, arg1: String, arg2: i32) -> Result<Response> {
// Implementation
}
}
// Client side
let client = MyServiceClient::connect(addr, config).await?;
let response = client.my_method("test".to_string(), 42).await?;
<span class="boring">}</span></code></pre></pre>
<h2 id="streaming"><a class="header" href="#streaming">Streaming</a></h2>
<h3 id="server-side-streaming"><a class="header" href="#server-side-streaming">Server-Side Streaming</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[rpc_trait]
pub trait StreamService {
async fn stream_data(&self, count: usize) -> impl Stream<Item = Result<Data>>;
}
#[rpc_impl]
impl StreamService for Handler {
async fn stream_data(&self, count: usize) -> impl Stream<Item = Result<Data>> {
futures::stream::iter(0..count).map(|i| {
Ok(Data { value: i })
})
}
}
<span class="boring">}</span></code></pre></pre>
<h3 id="client-side-streaming"><a class="header" href="#client-side-streaming">Client-Side Streaming</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[rpc_trait]
pub trait UploadService {
async fn upload(&self, stream: impl Stream<Item = Chunk>) -> Result<Summary>;
}
// Client usage
let chunks = futures::stream::iter(vec![chunk1, chunk2, chunk3]);
let summary = client.upload(chunks).await?;
<span class="boring">}</span></code></pre></pre>
<h3 id="bidirectional-streaming"><a class="header" href="#bidirectional-streaming">Bidirectional Streaming</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[rpc_trait]
pub trait ChatService {
async fn chat(&self, stream: impl Stream<Item = Message>)
-> impl Stream<Item = Result<Message>>;
}
<span class="boring">}</span></code></pre></pre>
<h2 id="load-balancing-strategies"><a class="header" href="#load-balancing-strategies">Load Balancing Strategies</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::LoadBalancingStrategy;
// Round Robin - even distribution
LoadBalancingStrategy::RoundRobin
// Random - stateless selection
LoadBalancingStrategy::Random
// Least Connections - pick least loaded (recommended)
LoadBalancingStrategy::LeastConnections
<span class="boring">}</span></code></pre></pre>
<h2 id="cluster-events"><a class="header" href="#cluster-events">Cluster Events</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::cluster::ClusterEvent;
let mut events = cluster.subscribe();
while let Some(event) = events.recv().await {
match event {
ClusterEvent::NodeJoined(node) => {
println!("Node {} joined at {}", node.id, node.addr);
}
ClusterEvent::NodeLeft(node) => {
println!("Node {} left", node.id);
}
ClusterEvent::NodeFailed(node) => {
println!("Node {} failed", node.id);
}
ClusterEvent::NodeUpdated(node) => {
println!("Node {} updated", node.id);
}
ClusterEvent::PartitionDetected(minority, majority) => {
println!("Partition detected!");
}
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="error-handling"><a class="header" href="#error-handling">Error Handling</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rpcnet::{Error, ErrorKind};
match client.call("method", args).await {
Ok(response) => {
// Handle success
}
Err(e) => {
match e.kind() {
ErrorKind::ConnectionFailed => {
// Connection issue, retry with different worker
}
ErrorKind::Timeout => {
// Request timed out
}
ErrorKind::SerializationError => {
// Data serialization failed
}
ErrorKind::ApplicationError => {
// Application-level error from handler
}
_ => {
// Other errors
}
}
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="common-patterns"><a class="header" href="#common-patterns">Common Patterns</a></h2>
<h3 id="health-check-endpoint"><a class="header" href="#health-check-endpoint">Health Check Endpoint</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[rpc_trait]
pub trait HealthService {
async fn health(&self) -> Result<HealthStatus>;
}
#[derive(Serialize, Deserialize)]
pub struct HealthStatus {
pub healthy: bool,
pub version: String,
pub uptime_secs: u64,
}
<span class="boring">}</span></code></pre></pre>
<h3 id="graceful-shutdown"><a class="header" href="#graceful-shutdown">Graceful Shutdown</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use tokio::signal;
async fn run(mut server: Server, cluster: Arc<ClusterMembership>) -> Result<()> {
let server_task = tokio::spawn(async move { server.run().await });
signal::ctrl_c().await?;
// Leave cluster gracefully
cluster.leave().await?;
// Wait for in-flight requests
server.shutdown().await?;
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<h3 id="connection-retry"><a class="header" href="#connection-retry">Connection Retry</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn call_with_retry<T>(
f: impl Fn() -> Pin<Box<dyn Future<Output = Result<T>>>>,
max_retries: usize,
) -> Result<T> {
for attempt in 0..max_retries {
match f().await {
Ok(result) => return Ok(result),
Err(e) if attempt < max_retries - 1 => {
tokio::time::sleep(Duration::from_millis(100 * 2_u64.pow(attempt as u32))).await;
}
Err(e) => return Err(e),
}
}
unreachable!()
}
<span class="boring">}</span></code></pre></pre>
<h2 id="environment-variables"><a class="header" href="#environment-variables">Environment Variables</a></h2>
<p>Common environment variables used in examples:</p>
<pre><code class="language-bash"># Director
DIRECTOR_ADDR=127.0.0.1:61000
RUST_LOG=info
# Worker
WORKER_LABEL=worker-1
WORKER_ADDR=127.0.0.1:62001
DIRECTOR_ADDR=127.0.0.1:61000
# Client
CLIENT_ID=client-1
# Logging
RUST_LOG=rpcnet=debug,my_app=info
</code></pre>
<h2 id="feature-flags"><a class="header" href="#feature-flags">Feature Flags</a></h2>
<pre><code class="language-toml">[dependencies]
rpcnet = { version = "0.2", features = ["cluster", "metrics"] }
</code></pre>
<p>Available features:</p>
<ul>
<li><code>cluster</code> - Enable cluster features (WorkerRegistry, ClusterClient, etc.)</li>
<li><code>metrics</code> - Enable Prometheus metrics</li>
<li><code>codegen</code> - Enable code generation support (always included in v0.2+)</li>
</ul>
<h2 id="quick-examples"><a class="header" href="#quick-examples">Quick Examples</a></h2>
<h3 id="simple-rpc-server"><a class="header" href="#simple-rpc-server">Simple RPC Server</a></h3>
<pre><pre class="playground"><code class="language-rust">use rpcnet::prelude::*;
#[rpc_trait]
pub trait Echo {
async fn echo(&self, msg: String) -> Result<String>;
}
#[rpc_impl]
impl Echo for Handler {
async fn echo(&self, msg: String) -> Result<String> {
Ok(msg)
}
}
#[tokio::main]
async fn main() -> Result<()> {
let config = ServerConfig::builder()
.with_cert_and_key(cert, key)?
.build();
let mut server = Server::new(config);
server.register_service(Arc::new(Handler));
server.bind("0.0.0.0:8080").await?;
server.run().await?;
Ok(())
}</code></pre></pre>
<h3 id="simple-rpc-client"><a class="header" href="#simple-rpc-client">Simple RPC Client</a></h3>
<pre><pre class="playground"><code class="language-rust">#[tokio::main]
async fn main() -> Result<()> {
let config = ClientConfig::builder()
.with_server_cert(cert)?
.build();
let client = EchoClient::connect("localhost:8080", config).await?;
let response = client.echo("Hello!".to_string()).await?;
println!("Response: {}", response);
Ok(())
}</code></pre></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li><strong><a href="examples.html">Examples</a></strong> - Complete example programs</li>
<li><strong><a href="../cluster/tutorial.html">Cluster Tutorial</a></strong> - Build a cluster</li>
<li><strong><a href="https://docs.rs/rpcnet">API Documentation</a></strong> - Full API docs</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<a rel="prev" href="../advanced/migration.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../reference/examples.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../advanced/migration.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../reference/examples.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
</div>
</body>
</html>