HN 提问:用 Rust 暴露分布式状态的方式可行吗?

1作者: asdfghjqwertyu15 天前
大家好, 我正在试验一个 Rust 运行时,它通过声明一个*过程宏*来公开*复制的、共识支持的状态*。客户端 crate 调用该宏并获得一个类型化的 API;运行时同时拥有协议和代码生成。 从高层次来看,数据库单元: * 处理 RPC(Upsert / Get / Remove) * 协调客户端 API 的宏扩展 * 存储不透明的零拷贝 blob(`rkyv`) 以下是数据库端的核心代码: ```rust use cell_sdk::*; use cell_model::macro_coordination::*; use std::pin::Pin; #[protein] pub struct Upsert { pub key: u64, pub kind: String, pub blob: Vec<u8>, } #[protein] pub struct Get { pub key: u64, pub kind: String } #[protein] pub struct Row { pub blob: Option<Vec<u8>> } #[service] #[derive(Clone)] struct DbService { state: Arc<RwLock<HashMap<(u64, String), Vec<u8>>>>, } #[handler] impl DbService { async fn upsert(&self, u: Upsert) -> Result<bool> { self.state.write().await.insert((u.key, u.kind), u.blob); Ok(true) } async fn get(&self, g: Get) -> Result<Row> { let val = self.state.read().await.get(&(g.key, g.kind)).cloned(); Ok(Row { blob: val }) } async fn remove(&self, k: u64, kind: String) -> Result<bool> { Ok(self.state.write().await.remove(&(k, kind)).is_some()) } } fn expand_table(ctx: &ExpansionContext) -> Pin<Box<dyn Future<Output=Result<String>> + Send + '_>> { Box::pin(async move { let struct_name = &ctx.struct_name; let pk = ctx.fields.first().unwrap().0.clone(); Ok(format!(r#" pub struct {struct_name}Table {{ synapse: ::cell_sdk::Synapse }} impl {struct_name}Table {{ pub async fn connect() -> ::anyhow::Result<Self> {{ Ok(Self {{ synapse: ::cell_sdk::Synapse::grow("db").await? }}) }} pub async fn save(&self, row: {struct_name}) -> ::anyhow::Result<bool> {{ let bytes = ::cell_sdk::rkyv::to_bytes::<_,1024>(&&row)?.into_vec(); let req = DbProtocol::Upsert {{ key: row.{pk}, kind: "{struct_name}".into(), blob: bytes, }}; self.synapse.fire(&req).await }} }} "#)) }) } #[tokio::main] async fn main() -> Result<()> { let db = DbService { state: Default::default() }; const macros = vec![MacroInfo { name: "table".into(), kind: MacroKind::Attribute, description: "distributed table".into(), dependencies: vec![], }]; Runtime::ignite_with_coordination( move |req| db.dispatch(req), "db", macros, expand_table, ).await } ``` 消费者 ```rust use cell_sdk::*; #[expand("db", "table")] #[derive(Archive, Serialize, Clone, Debug)] pub struct Order { pub order_id: u64, pub user_id: u64, pub amount: u64, } #[tokio::main] async fn main() -> anyhow::Result<()> { let orders = OrderTable::connect().await?; orders.save(Order { order_id: 42, user_id: 7, amount: 1000 }).await?; let o = orders.get(42).await?.unwrap(); println!("loaded {o:?}"); orders.remove(42).await?; Ok(()) } ``` 问题是:*让分布式服务同时拥有其协议和客户端宏是否有意义,或者这是否隐藏了过多的复杂性?* 以及:这真的对企业有用吗?你会使用它吗? 谢谢 — 正在寻找设计层面的反馈,而不是用户反馈。
查看原文
Hi HN,<p>I’m experimenting with a Rust runtime that exposes <i>replicated, consensus-backed state</i> by advertising a *procedural macro*. Client crates invoke the macro and get a typed API; the runtime owns both the protocol and the codegen.<p>At a high level, the DB cell:<p>* Handles RPC (Upsert &#x2F; Get &#x2F; Remove) * Coordinates macro expansion for client APIs * Stores opaque zero-copy blobs (`rkyv`)<p>Here’s the core of the DB side:<p>```rust use cell_sdk::<i>; use cell_model::macro_coordination::</i>; use std::pin::Pin;<p>#[protein] pub struct Upsert { pub key: u64, pub kind: String, pub blob: Vec&lt;u8&gt;, }<p>#[protein] pub struct Get { pub key: u64, pub kind: String }<p>#[protein] pub struct Row { pub blob: Option&lt;Vec&lt;u8&gt;&gt; }<p>#[service] #[derive(Clone)] struct DbService { state: Arc&lt;RwLock&lt;HashMap&lt;(u64, String), Vec&lt;u8&gt;&gt;&gt;&gt;, }<p>#[handler] impl DbService { async fn upsert(&amp;self, u: Upsert) -&gt; Result&lt;bool&gt; { self.state.write().await.insert((u.key, u.kind), u.blob); Ok(true) } async fn get(&amp;self, g: Get) -&gt; Result&lt;Row&gt; { let val = self.state.read().await.get(&amp;(g.key, g.kind)).cloned(); Ok(Row { blob: val }) } async fn remove(&amp;self, k: u64, kind: String) -&gt; Result&lt;bool&gt; { Ok(self.state.write().await.remove(&amp;(k, kind)).is_some()) } }<p>fn expand_table(ctx: &amp;ExpansionContext) -&gt; Pin&lt;Box&lt;dyn Future&lt;Output=Result&lt;String&gt;&gt; + Send + &#x27;_&gt;&gt; { Box::pin(async move { let struct_name = &amp;ctx.struct_name; let pk = ctx.fields.first().unwrap().0.clone();<p><pre><code> Ok(format!(r#&quot; pub struct {struct_name}Table {{ synapse: ::cell_sdk::Synapse }} impl {struct_name}Table {{ pub async fn connect() -&gt; ::anyhow::Result&lt;Self&gt; {{ Ok(Self {{ synapse: ::cell_sdk::Synapse::grow(&quot;db&quot;).await? }}) }} pub async fn save(&amp;self, row: {struct_name}) -&gt; ::anyhow::Result&lt;bool&gt; {{ let bytes = ::cell_sdk::rkyv::to_bytes::&lt;_,1024&gt;(&amp;row)?.into_vec(); let req = DbProtocol::Upsert {{ key: row.{pk}, kind: &quot;{struct_name}&quot;.into(), blob: bytes, }}; self.synapse.fire(&amp;req).await }} }} &quot;#)) })</code></pre> }<p>#[tokio::main] async fn main() -&gt; Result&lt;()&gt; { let db = DbService { state: Default::default() };<p><pre><code> const macros = vec![MacroInfo { name: &quot;table&quot;.into(), kind: MacroKind::Attribute, description: &quot;distributed table&quot;.into(), dependencies: vec![], }]; Runtime::ignite_with_coordination( move |req| db.dispatch(req), &quot;db&quot;, macros, expand_table, ).await</code></pre> } ```<p>consumer<p>```rust use cell_sdk::<i>;<p>#[expand(&quot;db&quot;, &quot;table&quot;)] #[derive(Archive, Serialize, Clone, Debug)] pub struct Order { pub order_id: u64, pub user_id: u64, pub amount: u64, }<p>#[tokio::main] async fn main() -&gt; anyhow::Result&lt;()&gt; { let orders = OrderTable::connect().await?;<p><pre><code> orders.save(Order { order_id: 42, user_id: 7, amount: 1000 }).await?; let o = orders.get(42).await?.unwrap(); println!(&quot;loaded {o:?}&quot;); orders.remove(42).await?; Ok(())</code></pre> } ```<p>The question: *does letting a distributed service own both its protocol and client-side macro make sense, or is this hiding too much complexity?* And: Would this actually be useful for enterprise? Would you use it?<p>Thanks — looking for design-level feedback, not users.</i>