今天的文章將帶讀者看看 Zenoh 如何從早期的 .res()
呼叫方式,演進到利用 Rust 穩定特性的 trait 驅動設計,提供更靈活、可維護、同時又直覺的 API。
.res()
作法在早期的 Zenoh Rust 版本中,API 會提供兩個獨立模組:一個給非同步程式設計,一個給同步程式設計,各自有自己的 prelude。
.res()
來等待 futures:use zenoh::prelude::r#async::*;
#[async_std::main]
async fn main() {
let session = zenoh::open(config::default()).res().await.unwrap();
session.put("key/expression", "value").res().await.unwrap();
session.close().res().await.unwrap();
}
.res()
,但會同步阻塞直到完成:use zenoh::prelude::sync::*;
fn main() {
let session = zenoh::open(config::default()).res().unwrap();
session.put("key/expression", "value").res().unwrap();
session.close().res().unwrap();
}
雖然能運作,但這種設計要求使用者使用兩套不同的 prelude,並且每次操作都要顯式 .res()
。這帶來了冗長、重複程式碼,以及 API 維護上的複雜度。
如今 Zenoh 使用單一 API,並透過 Rust 的穩定特性與 trait 抽象,讓 async-await 與同步可以一致地使用。
Resolvable
pub trait Resolvable {
type To: Sized;
}
這個 trait 代表「某個東西」可以被解析成型別 To
。同步與非同步操作都會實作它,抽象出解析的方式。
Wait
pub trait Wait: Resolvable {
/// 同步執行並等待結果
fn wait(self) -> Self::To;
}
Wait
擴充了 Resolvable
,提供同步阻塞執行。只要型別實作了 Wait
,就可以用 .wait()
來阻塞等待非同步操作的結果。
IntoFuture
Rust 從 1.64 版本(2022 年 9 月)起穩定了 IntoFuture
trait,允許任何型別被轉換成可搭配 .await
的 future。
Zenoh 在像 PublicationBuilder
這樣的型別上,同時實作 IntoFuture
與 Wait
,讓同一物件既能用 .await
,也能用 .wait()
。
impl<P, T> Resolvable for PublicationBuilder<P, T> {
type To = ZResult<()>;
}
impl Wait for PublicationBuilder<&Publisher<'_>, Put> {
fn wait(self) -> Self::To {
self.publisher.session.resolve_put(...)
}
}
impl IntoFuture for PublicationBuilder<&Publisher<'_>, Put> {
type Output = Self::To;
type IntoFuture = Ready<Self::To>;
fn into_future(self) -> Self::IntoFuture {
std::future::ready(self.wait())
}
}
在這裡:
.wait()
會同步阻塞等待 put 操作完成.into_future()
會把 .wait()
的結果包裝成一個 ready future.await
或 .wait()
,API 與型別都不用改ResolveFuture
Zenoh 把非同步 futures 包裝在一個型別 ResolveFuture
中:
pub struct ResolveFuture<F, To>(F) where
To: Sized + Send,
F: Future<Output = To> + Send;
impl<F, To> Wait for ResolveFuture<F, To>
where
To: Sized + Send,
F: Future<Output = To> + Send,
{
fn wait(self) -> To {
zenoh_runtime::ZRuntime::Application.block_in_place(self.0)
}
}
ResolveFuture
同時實作了 Resolvable
與 Wait
.wait()
使用 Zenoh runtime 的安全阻塞機制來等待 async future#[tokio::main]
async fn main() {
let session = zenoh::open(config).await.unwrap();
let publisher = session
.declare_publisher("example_keyexpr/**")
.encoding(...)
.congestion_control(...)
.priority(...)
.reliability(...)
.express()
.await
.unwrap();
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
publisher.put("payload").attachment(...).await.unwrap();
}
}
use zenoh::Wait;
fn main() {
let session = zenoh::open(config).wait().unwrap();
let publisher = session
.declare_publisher("example_keyexpr/**")
.encoding(...)
.congestion_control(...)
.priority(...)
.reliability(...)
.express()
.wait()
.unwrap();
loop {
std::thread::sleep(Duration::from_secs(1));
publisher.put("payload").attachment(...).wait().unwrap();
}
}
IntoFuture
Trait 來統一實踐Zenoh 的 Rust API 從最初明確使用 .res()
的分離 async/sync prelude,進化到現在基於 trait 的統一模型,利用:
Resolvable
Wait
IntoFuture
這些特性,提供了統一的 async-await 與同步阻塞體驗。
IntoFuture
在 Rust 1.64(2022 年 9 月)穩定後,才使這樣的優雅設計成為可能。
這種將 Rust 非同步能力與透明同步介面結合的方式,展現了如何設計 現代、靈活的函式庫,同時支援不同的 runtime 模型,而不犧牲優雅。
Zenoh 使用者因此能獲得簡單卻強大的 API,支撐多樣化的應用環境。
延伸閱讀: