rivetkit-client-rust

작성자: rivet-dev

RivetKit Rust 클라이언트 가이드. rivetkit::client를 사용하여 Rivet Actors에 연결하는 Rust 클라이언트 및 백엔드에 사용하며, 타입화된 액터 핸들을 생성하고 액션을 호출합니다.

npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-rust

RivetKit Rust Client

Use this skill when building Rust clients that connect to Rivet Actors with rivetkit::client.

Version

RivetKit version: 2.3.7

First Steps

  1. Add the dependency
    cargo add rivetkit anyhow async-trait
    cargo add serde --features derive
    cargo add tokio --features full
    
  2. Create a client with Client::new(ClientConfig::new(endpoint)) and call typed actions with get_or_create_typed_default::<A>(...).

Error Handling Policy

  • Prefer fail-fast behavior by default. Propagate anyhow::Result with ?.
  • Avoid swallowing errors with broad match arms unless absolutely needed.
  • If an error is handled inline, handle it explicitly, at minimum by logging it.

Rust support is in beta. The supported public Rust API is rivetkit and rivetkit::client; lower-level crates are internal implementation details and do not carry a stability guarantee. See the full API reference on docs.rs/rivetkit, or the runnable hello-world-rust example.

Getting Started

See the Rust quickstart guide for getting started.

Install

Add the rivetkit crate and its companions:

cargo add rivetkit anyhow async-trait
cargo add serde --features derive
cargo add tokio --features full

The Rust client is strongly typed. It shares the same action and event types as your actor, so define your actor in src/lib.rs and import those types from both your server and your client. There is no need to redefine the actor on the client. See Define Your Actor in the quickstart for the actor definition this page builds on.

Minimal Client

use counter::{Counter, Increment};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

	let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;
	let count = counter.send(Increment { amount: 1 }).await?;
	println!("New count: {count}");

	Ok(())
}

counter here is your crate name (the package name in Cargo.toml, with dashes as underscores). Counter and Increment are the types you defined alongside your actor.

Stateless vs Stateful

use counter::{Counter, Increment, NewCount};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
	let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;

	// Stateless: each call is independent
	counter.send(Increment { amount: 1 }).await?;

	// Stateful: keep a connection open for realtime events
	let connection = counter.connect();
	connection
		.on::<NewCount>(|event| println!("count: {}", event.count))
		.await;
	connection.send(Increment { amount: 1 }).await?;

	connection.disconnect().await;
	Ok(())
}

A stateless call on the handle opens a short-lived request per action. A connection keeps a WebSocket open so you can receive events and reuse it across calls.

Getting Actors

use counter::Counter;
use rivetkit::{
	client::{Client, ClientConfig, GetOrCreateOptions},
	prelude::*,
	TypedClientExt,
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

	// Get or create an actor
	let room = client.get_or_create_typed_default::<Counter>("counter", ["room-42"])?;

	// Get an existing actor handle (fails when used if the actor does not exist)
	let existing = client.get_typed_default::<Counter>("counter", ["room-42"])?;

	// Create a new actor with input
	let created = client.get_or_create_typed::<Counter>(
		"counter",
		["game-1"],
		GetOrCreateOptions {
			create_with_input: Some(json!({ "mode": "ranked" })),
			..Default::default()
		},
	)?;

	// Get an actor handle by ID
	let by_id = client.get_for_id("counter", "actor-id", Default::default())?;

	// Resolve the actor ID
	let resolved_id = room.inner().resolve().await?;
	println!("Resolved ID: {resolved_id}");

	Ok(())
}

get_typed_default / get_or_create_typed_default use default options. The non-default variants (get_typed / get_or_create_typed) take GetOptions / GetOrCreateOptions for connection parameters, input, and region. Set pool_name on GetOrCreateOptions or CreateOptions to override the client's configured pool for a single actor.

Connection Parameters

Pass connection parameters through the handle options. They are delivered to the actor's create_conn_state callback:

use counter::Counter;
use rivetkit::{
	client::{Client, ClientConfig, GetOrCreateOptions},
	prelude::*,
	TypedClientExt,
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

	let chat = client.get_or_create_typed::<Counter>(
		"counter",
		["general"],
		GetOrCreateOptions {
			params: Some(json!({ "authToken": "jwt-token-here" })),
			..Default::default()
		},
	)?;

	let connection = chat.connect();
	connection.disconnect().await;
	Ok(())
}

Subscribing to Events

on registers a typed callback for an event and returns once the subscription is registered:

use counter::{Counter, NewCount};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
	let connection = client
		.get_or_create_typed_default::<Counter>("counter", ["general"])?
		.connect();

	connection
		.on::<NewCount>(|event| println!("count changed: {}", event.count))
		.await;

	Ok(())
}

Event callbacks are synchronous and run for every matching event. The actor's emitted event type (here NewCount) is decoded into the typed value for you.

Connection Lifecycle

The lower-level connection exposes lifecycle callbacks and the current status. Reach it with connection.inner():

use counter::Counter;
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
	let connection = client
		.get_or_create_typed_default::<Counter>("counter", ["general"])?
		.connect();
	let inner = connection.inner().clone();

	inner.on_open(|| println!("connected")).await;
	inner.on_close(|| println!("disconnected")).await;
	inner.on_error(|message| eprintln!("error: {message}")).await;
	inner
		.on_status_change(|status| println!("status: {status:?}"))
		.await;

	println!("current status: {:?}", inner.conn_status());

	connection.disconnect().await;
	Ok(())
}

ConnectionStatus is one of Idle, Connecting, Connected, or Disconnected. Connections reconnect automatically with backoff until you call disconnect.

Low-Level HTTP & WebSocket

For actors that implement on_request or on_websocket, call them directly on the untyped handle (handle.inner()). fetch returns a reqwest::Response, and web_socket returns a tokio_tungstenite stream. This example also needs a few extra crates:

cargo add futures-util tokio-tungstenite
cargo add reqwest --features json
use counter::Counter;
use futures_util::{SinkExt, StreamExt};
use reqwest::{header::HeaderMap, Method};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};
use tokio_tungstenite::tungstenite::Message;

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
	let handle = client.get_or_create_typed_default::<Counter>("counter", ["general"])?;

	// Raw HTTP request
	let response = handle
		.inner()
		.fetch("history", Method::GET, HeaderMap::new(), None)
		.await?;
	let history: Vec<String> = response.json().await?;
	println!("history: {history:?}");

	// Raw WebSocket connection
	let mut ws = handle.inner().web_socket("stream", None).await?;
	ws.send(Message::text("hello")).await?;
	if let Some(message) = ws.next().await {
		println!("received: {:?}", message?);
	}

	Ok(())
}

Calling from Backend

The client is a normal Tokio type, so you can hold it in your backend (Axum, Actix, etc.) and call actors from request handlers. The client is Clone and cheap to share:

use counter::{Counter, Increment};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

async fn increment(client: Client) -> Result<i64> {
	let counter = client.get_or_create_typed_default::<Counter>("counter", ["server-counter"])?;
	let count = counter.send(Increment { amount: 1 }).await?;
	Ok(count)
}

Error Handling

Action and connection calls return anyhow::Result. Actor-side errors surface as an anyhow::Error carrying the error group, code, message, and metadata:

use counter::{Counter, Increment};
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
	let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;

	match counter.send(Increment { amount: 1 }).await {
		Ok(count) => println!("count: {count}"),
		Err(error) => eprintln!("action failed: {error:#}"),
	}

	Ok(())
}

Concepts

Keys

Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:

use counter::Counter;
use rivetkit::{
	client::{Client, ClientConfig},
	prelude::*,
	TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
	let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

	// Compound key: [org, room]
	let room = client.get_or_create_typed_default::<Counter>("counter", ["org-acme", "general"])?;
	let actor_id = room.inner().resolve().await?;
	println!("Actor ID: {actor_id}");

	Ok(())
}

Keys accept arrays of &str or String (["org-acme", "general"]). Don't build keys with string interpolation like format!("org:{user_id}") when user_id contains user data. Use arrays instead to prevent key injection attacks.

Configuration

ClientConfig::new(endpoint) is a builder. The endpoint is always required; there is no default. Common options:

use rivetkit::client::ClientConfig;

let config = ClientConfig::new("http://localhost:6420")
	.namespace("default")
	.token("pk_...")
	.pool_name("my-pool")
	.header("x-custom", "value");
  • namespace - target namespace (defaults to the engine's configured namespace).
  • token - authentication token for the engine.
  • pool_name - runner pool to target.
  • header / headers - extra HTTP headers sent with each request.
  • max_input_size - cap on encoded action input size.

For serverless deployments, set the endpoint to your app's /api/rivet URL. See Endpoints for details.

API Reference

See the full client API documentation on docs.rs/rivetkit-client.

Need More Than the Client?

If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:

npx skills add rivet-dev/skills

Then use the rivetkit skill for backend guidance.

rivet-dev의 다른 스킬

ai-agent
rivet-dev
지속적 메모리를 갖춘 AI 에이전트 백엔드 구축: 대화당 하나의 Rivet Actor, 대기열 메시지 처리, 실시간 이벤트로 스트리밍되는 LLM 응답.
official
ai-agent-workspace
rivet-dev
모든 AI 에이전트에게 자신만의 컴퓨터를 부여하세요: 경량 인프로세스 상에서 파일 시스템, 프로세스, 셸, 네트워킹 및 에이전트 세션을 갖춘 영구 작업 공간입니다…
official
chat-room
rivet-dev
Rivet Actors로 실시간 채팅방 백엔드 구축: 방마다 하나의 액터, SQLite 기반 메시지 기록, 모든 연결된 클라이언트에 WebSocket 브로드캐스트.
official
collaborative-text-editor
rivet-dev
Yjs CRDT와 Rivet Actors를 사용하여 협업 텍스트 편집기 백엔드를 구축합니다: 문서별 액터가 동기화 및 인식 업데이트를 중계하고 스냅샷을 유지합니다.
official
cron-jobs
rivet-dev
Rivet Actors를 사용한 내구성 있는 크론 작업: schedule.after 및 schedule.at 타이머는 재시작과 충돌에도 유지되며, 반복 작업 재설정 및 멱등성 핸들러를 지원합니다.
official
live-cursors
rivet-dev
Rivet Actors를 사용한 라이브 커서 및 멀티플레이어 프레즌스: 연결별 커서 상태, 이벤트 또는 원시 WebSocket을 통한 실시간 업데이트, 스로틀링.
official
per-tenant-database
rivet-dev
멀티 테넌트 데이터 격리를 위해 테넌트당 하나의 Rivet 액터를 사용합니다. 액터 키는 테넌트 ID이므로 각 테넌트는 자체 격리된 데이터셋과 마이그레이션을 갖습니다.
official
rivetkit-client-javascript
rivet-dev
JavaScript 클라이언트로, 무상태 또는 상태 저장 연결을 통해 Rivet Actors에 연결합니다. 브라우저, Node.js 및 Bun 환경을 지원하며, 환경 변수 또는 명시적 구성을 통한 자동 엔드포인트 감지 기능을 제공합니다. 독립적인 요청을 위한 무상태 액션 호출과 실시간 이벤트 구독이 가능한 상태 저장 연결의 두 가지 상호작용 모드를 제공합니다. onRequest 또는 onWebSocket 핸들러를 구현하는 액터를 위한 저수준 HTTP 및 WebSocket 액세스를 포함하며, 복합 배열 기반...
official