一个 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 的反序列化,带来一定的性能损失。
1
hsfzxjy 2022-05-21 13:34:51 +08:00
可以用 struct 来约束被 borrow 的 String 和 deserialize 出来的对象的 lifetime 关系。
https://gist.github.com/rust-play/37e864f6c06fe0a346b24151ed7fa7b4 ``` use serde::Deserialize; use serde_json; use std::marker::PhantomData; #[derive(Deserialize, Debug)] struct Ping<'a> { #[serde(borrow)] ping: &'a str, } struct Handler<T> { data: String, _marker: PhantomData<T>, } impl<'de, T: Deserialize<'de>> Handler<T> { fn new(data: String) -> Self { Self { data, _marker: PhantomData::default(), } } fn handle(&'de self, callback: impl FnOnce(T)) { let res: T = serde_json::from_str(&self.data).unwrap(); callback(res); } } fn main() { let callback = |p: Ping| println!("{:?}", p); Handler::<Ping>::new(format!("{{\"ping\":\"{}\"}}", "abcdefg")).handle(callback); } ``` |
2
hsfzxjy 2022-05-21 13:40:08 +08:00 1
脑抽了。。其实只要把 String 作为入参,并声明好 lifetime 约束就行了
https://gist.github.com/rust-play/c929dd0bfcf378abb0ba1668589e02af |
3
chuanqirenwu OP > 脑抽了。。其实只要把 String 作为入参,并声明好 lifetime 约束就行了
@hsfzxjy 谢谢,这种方式似乎不太可行,请看这个: https://gist.github.com/d52e5de95efe63d43ba7b7a8d45b4c1e 现有的接口设计下,`msg: &'a str` 这个参数恐怕无法从外部传入。 |
4
hsfzxjy 2022-05-21 14:48:07 +08:00
@chuanqirenwu #3 那就套一层函数调用呗,21 行开始那里改成调用 handle(&self.content, callback)
|
5
chuanqirenwu OP @hsfzxjy 尝试过,生命周期问题还是存在,另外还多了个 callback 被 move 的问题。
|
6
hsfzxjy 2022-05-21 14:51:38 +08:00
或者你 fn run() 的签名变一下
pub fn run<'a: 'de, 'de, T: Deserialize<'de>, Callback: Fn(T)>(&'a self, callback: Callback) 原理是一样的 |
7
chuanqirenwu OP @hsfzxjy sorry ,楼上说的方法原理不太明白,我试了一下似乎还是报同样的错误。您能说的详细一点吗?
|