# Tour of the Rust SDK
import { Callout } from 'nextra/components'
A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth.
The Mixnet is not like regular internet networking. There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination.
The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams.
## Send a raw message payload
The message API gives you direct access to the Mixnet's native communication model: individually addressed payloads with no connections and no ordering guarantees. This is useful when you want full control, but it's not how most networking code works:
```rust
use nym_sdk::mixnet::{self, MixnetMessageSender};
#[tokio::main]
async fn main() {
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
let addr = *client.nym_address();
println!("Connected: {addr}");
// Send a message to ourselves
client
.send_plain_message(addr, "hello mixnet!")
.await
.unwrap();
// Receive it (filter empty SURB management messages)
if let Some(msgs) = client.wait_for_messages().await {
for msg in msgs.iter().filter(|m| !m.message.is_empty()) {
println!("Got: {}", String::from_utf8_lossy(&msg.message));
}
}
// Always disconnect for clean shutdown
client.disconnect().await;
}
```
The message is Sphinx-encrypted, mixed across 5 nodes, and reconstructed on arrival. The whole round trip takes a few seconds.
Next: [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial)
## Reply anonymously with SURBs
Every received message carries a `sender_tag`, an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs):
```rust
// After receiving a message...
let tag = received_msg.sender_tag.expect("message includes sender tag");
client.send_reply(tag, "anonymous reply!").await.unwrap();
```
The replying side never learns where the reply is going, enabling anonymous communication without mutual identity disclosure.
## Open a bidirectional stream
If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite`, so any code that works with sockets works with `MixnetStream`:
```rust
use nym_sdk::mixnet;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() {
let mut sender = mixnet::MixnetClient::connect_new().await.unwrap();
let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap();
let recv_addr = *receiver.nym_address();
// Receiver creates a listener (activates stream mode)
let mut listener = receiver.listener().unwrap();
// Sender opens a stream to the receiver
let mut out = sender.open_stream(recv_addr, None).await.unwrap();
// Receiver accepts it
let mut inc = listener.accept().await.unwrap();
// Standard tokio I/O: write, flush, read
out.write_all(b"hello stream").await.unwrap();
out.flush().await.unwrap();
let mut buf = vec![0u8; 1024];
let n = inc.read(&mut buf).await.unwrap();
println!("{}", String::from_utf8_lossy(&buf[..n]));
drop(out);
drop(inc);
sender.disconnect().await;
receiver.disconnect().await;
}
```
Activating stream mode (by calling `listener()` or `open_stream()`) disables message-based methods like `send_plain_message()` and `wait_for_messages()`. A single client operates in one mode at a time.
Next: [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial)
## Use a client pool for bursty traffic
Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The [Client Pool](./client-pool) pre-creates clients in the background so they're ready when you need them:
```rust
use nym_sdk::client_pool::ClientPool;
#[tokio::main]
async fn main() {
let pool = ClientPool::new(3); // maintain 3 clients in reserve
let bg = pool.clone();
tokio::spawn(async move { bg.start().await });
// Wait for pool to fill, then grab a ready client
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
if let Some(client) = pool.get_mixnet_client().await {
println!("Got client: {}", client.nym_address());
client.disconnect().await;
}
pool.disconnect_pool().await;
}
```
Clients are consumed, not returned; the pool creates replacements automatically.
Next: [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial)
## Persist your identity
By default, `connect_new()` creates ephemeral keys that are discarded on disconnect. To keep the same Nym address across restarts, use the builder with on-disk storage:
```rust
use nym_sdk::mixnet::{MixnetClientBuilder, StoragePaths};
use std::path::PathBuf;
let storage = StoragePaths::new_from_dir(
&PathBuf::from("/tmp/my-nym-client")
).unwrap();
let client = MixnetClientBuilder::new_with_default_storage(storage)
.await
.unwrap()
.build()
.unwrap()
.connect_to_mixnet()
.await
.unwrap();
// This address is the same every time you run with the same path
println!("Persistent address: {}", client.nym_address());
```
## Where to go next
- [Installation](./importing): add `nym-sdk` to your project
- [Mixnet Tutorial](./mixnet/tutorial): send, receive, and reply with SURBs
- [Stream Tutorial](./stream/tutorial): build a private echo server
- [Client Pool Tutorial](./client-pool/tutorial): handle bursty traffic
- [API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/): type details, method signatures, architecture docs