HN 提问:用 Rust 暴露分布式状态的方式可行吗?
1 分•作者: asdfghjqwertyu•15 天前
大家好,
我正在试验一个 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 / Get / 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<u8>,
}<p>#[protein]
pub struct Get { pub key: u64, pub kind: String }<p>#[protein]
pub struct Row { pub blob: Option<Vec<u8>> }<p>#[service]
#[derive(Clone)]
struct DbService {
state: Arc<RwLock<HashMap<(u64, String), Vec<u8>>>>,
}<p>#[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())
}
}<p>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();<p><pre><code> 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
}}
}}
"#))
})</code></pre>
}<p>#[tokio::main]
async fn main() -> Result<()> {
let db = DbService { state: Default::default() };<p><pre><code> 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</code></pre>
}
```<p>consumer<p>```rust
use cell_sdk::<i>;<p>#[expand("db", "table")]
#[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() -> anyhow::Result<()> {
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!("loaded {o:?}");
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>