rivetkit-client-rust

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

RivetKit Rust Client

RivetKit Rust客户端

Use this skill when building Rust clients that connect to Rivet Actors with
rivetkit::client
.
当构建通过
rivetkit::client
连接到Rivet Actors的Rust客户端时,请使用本指南。

Version

版本

RivetKit version: 2.3.2
RivetKit版本:2.3.2

First Steps

入门步骤

  1. Add the dependency
    sh
    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>(...)
    .
  1. 添加依赖
    sh
    cargo add rivetkit anyhow async-trait
    cargo add serde --features derive
    cargo add tokio --features full
  2. 使用
    Client::new(ClientConfig::new(endpoint))
    创建客户端,并通过
    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.
  • 默认优先采用快速失败机制。通过
    ?
    传播
    anyhow::Result
  • 除非绝对必要,否则避免使用宽泛的
    match
    分支掩盖错误。
  • 如果在代码内处理错误,请显式处理,至少要记录日志。
Rust支持目前处于测试版。受支持的公开Rust API为
rivetkit
rivetkit::client
;底层crate属于内部实现细节,不提供稳定性保证。请查看docs.rs/rivetkit上的完整API参考,或可运行的
hello-world-rust
示例。

Getting Started

快速开始

See the Rust quickstart guide for getting started.
请查看Rust快速入门指南以开始使用。

Install

安装

Add the
rivetkit
crate and its companions:
sh
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.
添加
rivetkit
crate及其配套依赖:
sh
cargo add rivetkit anyhow async-trait
cargo add serde --features derive
cargo add tokio --features full
Rust客户端是强类型的。它与你的actor共享相同的操作和事件类型,因此请在
src/lib.rs
中定义你的actor,并从服务器和客户端导入这些类型。无需在客户端重新定义actor。请查看快速入门中的定义你的Actor部分,了解本页面所基于的actor定义。

Minimal Client

最小化客户端

rust
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.
rust
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
是你的crate名称(
Cargo.toml
中的package
name
,短横线替换为下划线)。
Counter
Increment
是你与actor一起定义的类型。

Stateless vs Stateful

无状态与有状态

rust
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.
rust
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(())
}
句柄上的无状态调用会为每个操作打开一个短连接。而连接会保持WebSocket打开,以便你接收事件并在多次调用中复用它。

Getting Actors

获取Actor

rust
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.
rust
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
使用默认选项。非默认变体(
get_typed
/
get_or_create_typed
)接受
GetOptions
/
GetOrCreateOptions
用于连接参数、输入和区域配置。

Connection Parameters

连接参数

Pass connection parameters through the handle options. They are delivered to the actor's
create_conn_state
callback:
rust
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(())
}
通过句柄选项传递连接参数。它们会被传递到actor的
create_conn_state
回调中:
rust
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:
rust
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.
on
方法为事件注册一个类型化回调,注册完成后返回:
rust
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(())
}
事件回调是同步的,会为每个匹配的事件运行。actor发出的事件类型(此处为
NewCount
)会被解码为类型化值供你使用。

Connection Lifecycle

连接生命周期

The lower-level connection exposes lifecycle callbacks and the current status. Reach it with
connection.inner()
:
rust
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
.
底层连接暴露了生命周期回调和当前状态。通过
connection.inner()
获取它:
rust
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
的取值为
Idle
Connecting
Connected
Disconnected
。在你调用
disconnect
之前,连接会自动退避重连。

Low-Level HTTP & WebSocket

底层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:
sh
cargo add futures-util tokio-tungstenite
cargo add reqwest --features json
rust
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(())
}
对于实现了
on_request
on_websocket
的actor,可以在非类型化句柄(
handle.inner()
)上直接调用它们。
fetch
返回
reqwest::Response
web_socket
返回
tokio_tungstenite
流。本示例还需要几个额外的crate:
sh
cargo add futures-util tokio-tungstenite
cargo add reqwest --features json
rust
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:
rust
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)
}
客户端是标准的Tokio类型,因此你可以在后端(Axum、Actix等)中持有它,并从请求处理程序中调用actor。客户端支持
Clone
,且共享成本低:
rust
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:
rust
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(())
}
操作和连接调用返回
anyhow::Result
。Actor端的错误会以
anyhow::Error
的形式呈现,包含错误组、代码、消息和元数据:
rust
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:
rust
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.
键用于唯一标识actor实例。使用复合键(数组)进行分层寻址:
rust
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(())
}
键接受
&str
String
数组(如
["org-acme", "general"]
)。当
user_id
包含用户数据时,不要使用字符串插值(如
format!("org:{user_id}")
)来构建键。请改用数组以防止键注入攻击。

Configuration

配置

ClientConfig::new(endpoint)
is a builder. The endpoint is always required; there is no default. Common options:
rust
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.
ClientConfig::new(endpoint)
是一个构建器。endpoint是必填项,没有默认值。常见选项:
rust
use rivetkit::client::ClientConfig;

let config = ClientConfig::new("http://localhost:6420")
	.namespace("default")
	.token("pk_...")
	.pool_name("my-pool")
	.header("x-custom", "value");
  • namespace
    - 目标命名空间(默认为引擎配置的命名空间)。
  • token
    - 引擎的认证令牌。
  • pool_name
    - 目标运行池。
  • header
    /
    headers
    - 随每个请求发送的额外HTTP头。
  • max_input_size
    - 编码后操作输入的大小上限。
对于无服务器部署,请将endpoint设置为你的应用的
/api/rivet
URL。详情请查看Endpoints

API Reference

API参考

See the full client API documentation on docs.rs/rivetkit-client.
请查看docs.rs/rivetkit-client上的完整客户端API文档。

Need More Than the Client?

除了客户端还需要更多?

If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
bash
npx skills add rivet-dev/skills
Then use the
rivetkit
skill for backend guidance.
如果你需要了解更多关于Rivet Actors、注册表或服务器端RivetKit的信息,请添加主技能:
bash
npx skills add rivet-dev/skills
然后使用
rivetkit
技能获取后端相关指南。