背景
一个 WebSocket 客户端,当接收到服务端推送的各种类型的消息后,调用用户传入的回调函数处理消息。
假设只有一种消息,容易写出一个非 Generic 的版本:
use serde::Deserialize;
use serde_json;
#[derive(Deserialize, Debug)]
struct Ping<'a> {
#[serde(borrow)]
ping: &'a str,
}
fn handle<Callback: Fn(Ping)>(callback: Callback) {
// msg 可以是从 WebSocket 连接获取的消息,示例为了简单起见,直接写死。
let msg = format!("{{\"ping\":\"{}\"}}", "abcdefg");
let res: Ping = serde_json::from_str(&msg).unwrap();
callback(res);
}
fn main() {
let callback = |p: Ping| println!("{:?}", p);
handle(callback);
// 输出结果:Ping { ping: "abcdefg" }
}
基于此,为了让回调函数接收的参数类型由用户决定,我把 handle 写成一个 Generic 方法。
use serde::Deserialize;
use serde_json;
#[derive(Deserialize, Debug)]
struct Ping<'a> {
#[serde(borrow)]
ping: &'a str,
}
fn handle<'de, T: Deserialize<'de>, Callback: Fn(T)>(callback: Callback) {
let msg = format!("{{\"ping\":\"{}\"}}", "abcdefg");
let res: T = serde_json::from_str(&msg).unwrap();
callback(res);
}
fn main() {
let callback = |p: Ping| println!("{:?}", p);
handle(callback);
}
这种写法报编译错误:
10 | fn handle<'de, T: Deserialize<'de>, Callback: Fn(T)>(callback: Callback) {
| --- lifetime `'de` defined here
11 | let msg = format!("{{\"ping\":\"{}\"}}", "abcdefg");
12 | let res: T = serde_json::from_str(&msg).unwrap();
| ---------------------^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `msg` is borrowed for `'de`
13 | callback(res);
14 | }
| - `msg` dropped here while still borrowed
从编译器的角度可以理解为什么报错,T 应该具有生命周期 'de,但是 &msg 因为是局部变量,生命周期短于 'de。然而从开发者的角度来说,代码足以保证不存在失效的引用。
问题:
- 是否有保持接口设计不变的情况下,解决编译的问题?
- 如果是因为 Rust 或者 serde 库的限制,这种 pattern 是不可行的,那么有没有其他比较好的 pattern 绕过这个问题呢?
参考的资料:
Deserializer lifetimes · Serde
[help] Generic deserialize wrapper · Issue #450 · serde-rs/json (github.com)
rust - Lifetime error with Serde in a Generic Function - Stack Overflow
PS:
参考资料给出的解决方案都是建议将 T 约束为 DeserializeOwned,但这种方式就无法实现零 copy 的反序列化,带来一定的性能损失。