一个 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
。然而从开发者的角度来说,代码足以保证不存在失效的引用。
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
参考资料给出的解决方案都是建议将 T 约束为 DeserializeOwned
,但这种方式就无法实现零 copy 的反序列化,带来一定的性能损失。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.