iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
Rust

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

Day 21: Zenoh 無所不在:回顧 Zenoh 如何從 Rust 出發,實踐跨語言綁定 (Binding)

  • 分享至 

  • xImage
  •  

Zenoh 無所不在:回顧 Zenoh 如何從 Rust 出發,實踐跨語言綁定 (Binding)

今天我們來總結一下我們這幾天深入探討的各種綁定 (Binding)!

Zenoh 的核心是一個高效能的 pub/sub、storage 與 computation protocol。
它以 Rust 語言編寫,選擇 Rust 是因為它能保證記憶體安全(防止崩潰和安全漏洞)和無畏並行(fearless concurrency)。
這些特性對於一個可靠、高吞吐量的網路 protocol 至關重要。
然而不得不提到 Zenoh 能夠連接以主流語言編寫、在各種平台上運行的應用程式。
這是透過一個圍繞核心 Rust 實作的、複雜的 language bindings 生態系統來實現的。


1. 基礎:橋接 Rust 與 C/C++ (zenoh-c & zenoh-cpp)

建立與 C 的橋樑是 Zenoh 互操作性的基石。這同時也是最具挑戰性的部分,因为它要求將由編譯器強制執行的 Rust 嚴格所有權(ownership)和安全保證,映射到 C 的手動記憶體模型上。

zenoh-c 透過一個多層架構來應對此挑戰:

  • 自動化程式碼生成:建置過程使用一種稱為「基於編譯的內省(compilation-based introspection)」的技巧,來生成與 C 相容的「不透明型別(opaque types)」。這確保 C 程式碼能為 Rust 型別分配正確大小的記憶體,而無需暴露其內部結構,從而保持 ABI 穩定性並隱藏 Rust 特有的佈局優化。
  • 將 Rust 的所有權帶入 C:為了彌合差距,zenoh-c 引入了一套獨特的型別系統,這是對 Rust borrow checker 所強制執行模式的直接手動實作。資源被包裝在 z_owned_..._t(模擬 Rust 的 owned types)、z_loaned_..._t(模擬 Rust 的 borrows &T)和 z_moved_..._t(用於轉移所有權)型別中。這使得記憶體管理比典型的 C API 更加明確和安全。
  • 安全機制:雖然不如 Rust 編譯器那樣萬無一失,zenoh-c 實作了「墓碑(gravestone)」模式,標記已 drop 的物件,以幫助在偵錯時檢測 use-after-free 錯誤。

zenoh-cpp 在此 C 基礎上建構,提供了一種更貼近 Rust 精神的現代 C++ 體驗:

  • RAII 作為 Rust Drop Trait 的鏡像:該函式庫提供僅需 header 的 C++ 包裝器,使用 RAII(Resource Acquisition Is Initialization)自動管理 Zenoh 物件的生命週期。這是 Rust Drop trait 在 C++ 中的慣用對應實作,完全消除了手動呼叫清理函式的需要,並防止了資源洩漏。
  • 零成本抽象(Zero-Cost Abstraction):透過大量使用模板(templates)和內聯(inlining),C++ API 提供了現代化的功能,且沒有執行期開銷,忠實於 Rust 和 C++ 共享的零成本哲學。

2. 高階語言的威力:Python Bindings (zenoh-python)

zenoh-python 函式庫在成熟的 PyO3 框架之上,橋接了 Rust 的高效能與 Python 的易用性。

  • 橋接 Async Rust 與 Sync Python:一個核心挑戰是 Zenoh Rust 核心是完全非同步且多執行緒的。為防止 Python 的單執行緒全域直譯器鎖(GIL)成為瓶頸,zenoh-python 會在呼叫阻塞的 Rust 函式前,智慧地釋放 GIL。這使得高效能的 Rust 調度器可以在其他執行緒上不受阻礙地運行其網路 I/O 任務。
  • 慣用的 API 設計:此 binding 使用 macro 和基於 trait 的轉換系統(IntoRust/IntoPython),以 Pythonic 的方式暴露 Rust 的功能。例如,Rust 的 builder patterns 被映射為熟悉的 Python keyword arguments。

3. JVM 與 Android 整合:zenoh-kotlin

zenoh-kotlin 透過 Java Native Interface (JNI) 將 Zenoh 帶入 JVM 和 Android 生態系統。

  • 安全地共享 Rust 記憶體:所有權透過 Arc<T>(Rust 原生的執行緒安全引用計數工具)在 JNI 邊界進行管理。Arc 作為原始指標(raw pointer)傳遞給 JVM。為了操作它,Rust 程式碼從指標重構 Arc,然後使用 std::mem::forget 來防止其被釋放,從而有效地將所有權「歸還」給 JVM。這將 Rust 的記憶體安全模型擴展到 JVM 的託管記憶體世界。
  • 執行緒安全的回呼:當需要調用回呼時,Rust binding 會將當前執行緒附加到 JVM,確保所有對 Kotlin 程式碼的呼叫都是安全的,並尊重兩種環境的執行緒模型。

4. 延伸應用:嵌入式與網頁(zenoh-pico & zenoh-ts

  • zenoh-pico:對於資源最受限的環境,zenoh-pico 提供了一個原生的 C 語言 protocol 實作(而非對 Rust 核心的 binding)。做出此選擇是因為,即使是最小的 Rust 執行檔,對於某些微控制器來說也可能過於龐大,以及目前Zenoh Rust本身尚未完全支援no_std。它保持了與 zenoh-c 的 API 相容性。
  • zenoh-ts:對於網頁開發者,zenoh-ts 允許 JavaScript/TypeScript 透過 WebSockets 與基於 Rust 的 Zenoh router 進行通訊,有效地將核心 Rust 引擎的觸角延伸到瀏覽器中。

API:跨語言比較

為了了解這些設計哲學如何轉化為開發者體驗,讓我們比較在不同 binding 中執行簡單「put」操作的方式。每個範例最終都會呼叫相同的底層 Rust 函式。

zenoh (Native Rust)

// Native Rust 使用 async/await 並透過 Drop trait 自動進行資源清理
async fn main() {
    let session = zenoh::open(zenoh::Config::default()).await.unwrap();
    let publisher = session.declare_publisher("demo/rust").await.unwrap();
    publisher.put("Hello!").await.unwrap();
    // Session 和 publisher 在離開作用域時會被自動關閉
}

這是baseline。此 API 簡潔、完全非同步,並利用了 Rust 最強大的特性:記憶體安全和確定性的資源管理(透過 Drop trait),因此不需要手動呼叫 close()。*

zenoh-c

// 手動資源管理是明確的,在 C 中模仿 Rust 的所有權
z_owned_config_t config = z_config_default();
z_owned_session_t s = z_open(z_move(config));
z_owned_publisher_t pub = z_declare_publisher(z_loan(s), z_keyexpr("demo/c"), NULL);
z_publisher_put(z_loan(pub), (const uint8_t *)"Hello!", 6, NULL);

// 需要手動清理,像是手動版本的 Rust Drop trait
z_drop(z_move(pub));
z_drop(z_move(s));

zenoh-cpp (C++)

// RAII,即 C++ 版本的 Rust Drop,會自動處理清理
try {
    auto session = Session::open(Config::create_default());
    auto publisher = session.declare_publisher("demo/cpp");
    publisher.put("Hello!");
} catch (const ZException& e) { /* ... */ }

zenoh-python

# 'with' 陳述式提供了確定性的清理,類似於 Rust 的 Drop
with zenoh.open(zenoh.Config()) as session:
    publisher = session.declare_publisher("demo/python")
    publisher.put("Hello!")

zenoh-kotlin

// 包裝了原生 Rust 物件的物件導向 API
val session = Zenoh.open(Config.default()).getOrThrow()
val publisher = session.declarePublisher("demo/kotlin").getOrThrow()
publisher.put("Hello!").getOrThrow()
session.close() // 清理工作與 JVM 的 AutoCloseable 掛鉤

zenoh-ts (TypeScript/JavaScript)

// 與 Rust router 通訊的現代 async/await API
const session = await Session.open(new Config("ws/127.0.0.1:10000"));
const publisher = await session.declarePublisher("demo/ts");
await publisher.put("Hello!");
await session.close();

結論

Zenoh 專案展示了跨語言軟體開發的典範。其生態系統的成功源於深度利用 Rust 的核心優勢——效能、記憶體安全和無畏的並行——同時將這些優點細緻地轉化為對每種目標語言來說自然且慣用的模式。這種哲學使 Zenoh 成為一個真正通用的 protocol,並且由單一、可靠的 Rust 核心驅動。


上一篇
Day 20: 上至網頁前端下至嵌入式裝置:探索zenoh-pico & zenoh-ts
下一篇
Day 22: 使用 Rust 剖析 Zenoh:深入了解 Wireshark 插件 zenoh-dissector
系列文
30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言