iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
Rust

30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊系列 第 13

Day 13: Zenoh 如何在 Rust 中用現代 Trait-Based API 統一同步與非同步

  • 分享至 

  • xImage
  •  

Zenoh 如何在 Rust 中用現代 Trait-Based API 統一同步與非同步

今天的文章將帶讀者看看 Zenoh 如何從早期的 .res() 呼叫方式,演進到利用 Rust 穩定特性的 trait 驅動設計,提供更靈活、可維護、同時又直覺的 API。


舊的 Async 與 Sync API .res() 作法

在早期的 Zenoh Rust 版本中,API 會提供兩個獨立模組:一個給非同步程式設計,一個給同步程式設計,各自有自己的 prelude。

Async 程式需要顯式呼叫 .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();
}

Sync 程式同樣使用 .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:基於 Trait 的統一 Async/Sync API

如今 Zenoh 使用單一 API,並透過 Rust 的穩定特性與 trait 抽象,讓 async-await 與同步可以一致地使用。

關鍵的 Rust Traits

Resolvable

pub trait Resolvable {
    type To: Sized;
}

這個 trait 代表「某個東西」可以被解析成型別 To。同步與非同步操作都會實作它,抽象出解析的方式。

Wait

pub trait Wait: Resolvable {
    /// 同步執行並等待結果
    fn wait(self) -> Self::To;
}

Wait 擴充了 Resolvable,提供同步阻塞執行。只要型別實作了 Wait,就可以用 .wait() 來阻塞等待非同步操作的結果。

Async 與 Sync 的雙重性:IntoFuture

Rust 從 1.64 版本(2022 年 9 月)起穩定了 IntoFuture trait,允許任何型別被轉換成可搭配 .await 的 future。

Zenoh 在像 PublicationBuilder 這樣的型別上,同時實作 IntoFutureWait,讓同一物件既能用 .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 與型別都不用改

處理純 Futures: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 同時實作了 ResolvableWait
  • .wait() 使用 Zenoh runtime 的安全阻塞機制來等待 async future
  • 確保同步程式碼能在對應的ZRuntime上執行 async 任務,而不會造成死鎖或 runtime 衝突

使用範例:同一 API,兩種風格

Async

#[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();
    }
}

Sync

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();
    }
}

為什麼這個設計很重要

  • 易用性: 同一 API 同時支援 async 與 sync 呼叫
  • 一致性: 不再需要分開的 sync/async API,利用 Rust 的標準IntoFutureTrait 來統一實踐
  • 靈活性: sync 程式碼可以安全呼叫 async 內部邏輯
  • 可維護性: 對 Zenoh 的開發者與使用者都更輕鬆

回顧

Zenoh 的 Rust API 從最初明確使用 .res() 的分離 async/sync prelude,進化到現在基於 trait 的統一模型,利用:

  • Resolvable
  • Wait
  • IntoFuture

這些特性,提供了統一的 async-await 與同步阻塞體驗。

IntoFuture 在 Rust 1.64(2022 年 9 月)穩定後,才使這樣的優雅設計成為可能。

這種將 Rust 非同步能力與透明同步介面結合的方式,展現了如何設計 現代、靈活的函式庫,同時支援不同的 runtime 模型,而不犧牲優雅。
Zenoh 使用者因此能獲得簡單卻強大的 API,支撐多樣化的應用環境。

延伸閱讀:


上一篇
Day 12: Zenoh 的 非同步 Runtime 抉擇之路
下一篇
Day 14: 在 Zenoh 中橋接 Rust 與 C — 第 1 部分:架構與程式碼生成
系列文
30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言