如何通用化红豆杉网络请求



我正在尝试弄清楚如何使紫杉示例中的此 Web 请求代码成为枚举的反序列化类型和变体的通用代码。

// Deserialize type
#[derive(Debug, Deserialize)]
pub struct TagsResponse {
tags: Vec<String>,
}
// Enum variants
pub enum Msg {
TagsLoaded(Result<TagsResponse, Error>),
TagsLoadError,
}
// Working non-generic inline code
let callback = model.link.send_back(
//       want to make TagsResponse generic ⤵ 
move |response: Response<Json<Result<TagsResponse, Error>>>| {
let (meta, Json(data)) = response.into_parts();
if meta.status.is_success() {
//  ↓ and be able to pass in an enum value
Msg::TagsLoaded(data)
} else {
//  ↓ and be able to pass in an enum value
Msg::TagsLoadError
}
},
);
let request = Request::get(format!("{}{}", API_ULR, "tags"))
.body(Nothing)
.unwrap();
let task = model.fetch_service.fetch(request, callback);
model.fetch_task.push(task);

这是我所得到的,这似乎非常接近,但我已经进入了编译器之后的循环:

fn remote_get<T: 'static>(
fetch_service: &mut FetchService,
link: &mut ComponentLink<Model>,
success_msg: fn(Result<T, Error>) -> Msg,
error_msg: Msg,
) -> FetchTask
where
for<'de> T: serde::Deserialize<'de>,
{
let callback = link.send_back(move |response: Response<Json<Result<T, Error>>>| {
let (meta, Json(data)) = response.into_parts();
if meta.status.is_success() {
success_msg(data)
} else {
error_msg
}
});
let request = Request::get(format!("{}{}", API_ULR, "articles?limit=10&offset=0"))
.body(Nothing)
.unwrap();
fetch_service.fetch(request, callback)
}

使用呼叫站点:

let task = remote_get(
&mut self.fetch_service,
&mut self.link,
Msg::TagsLoaded,
Msg::TagsLoadError,
);
self.fetch_task.push(task);

生产:

|
598 |     error_msg: Msg,
|     --------- captured outer variable
...
608 |             error_msg
|             ^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

奇怪的是,如果我从参数列表中删除error_msg并简单地硬编码Msg::TagsLoadError它将编译,但随后请求不会运行。 🤷 ♂️

ComponentLink::send_back()预计Fn关闭。但是,您的闭包正在消耗捕获的变量,即error_msg,因此它只能调用一次。这使得你的闭包实现FnOnce而不是Fn,所以它不能在那里使用。

查看此情况的一种更简单方法是:

struct Foo;
fn call(f: impl Fn() -> Foo) {}
fn test(x: Foo) {
let cb = move || x;
call(cb);
}

完整的错误消息更清晰一些:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
--> src/lib.rs:6:14
|
6 |     let cb = move || x;
|              ^^^^^^^^-
|              |       |
|              |       closure is `FnOnce` because it moves the variable `x` out of its environment
|              this closure implements `FnOnce`, not `Fn`
7 |     call(cb);
|     ---- the requirement to implement `Fn` derives from here

这是有道理的;如果你写了几次call(cb)会发生什么?请记住,Foo不可复制或克隆。

确切地说,最简单的解决方案是使您的类型可克隆,以便可以重用:

let cb = move || {
x.clone()
};

它有效!

如果您不想要克隆的成本,则可以添加一些解决方法,例如传递返回错误的函数或某种引用计数指针。例如:

struct Foo;
fn call(f: impl Fn() -> Foo) {}
fn test(build_x: impl Fn() -> Foo) {
let cb = move || build_x();
call(cb);
}

这是有效的build_xFn,而不是FnOnce,因此在使用时不会消耗它,也就是说,您可以根据需要多次调用它。

另一种不带回调的解决方法是使用Option并使用Option::take来使用它。这用None替换了它,从借用检查器的角度来看,这个值继续存在。但是,您需要一个RefCell,否则您将改变捕获的变量并将闭包转换为FnMut

use std::cell::RefCell;
struct Foo;
fn call(f: impl Fn() -> Foo) {}
fn test(x: Foo) {
let ox = RefCell::new(Some(x));
let cb = move || ox.borrow_mut().take().unwrap();
call(cb);
}

更新到最后一个选项

当一个简单的Cell就可以使用RefCell时,不要使用。Cell有一个take成员函数,使这段代码更简单:

use std::cell::Cell;
struct Foo;
fn call(f: impl Fn() -> Foo) {}
fn test(x: Foo) {
let ox = Cell::new(Some(x));
let cb = move || ox.take().unwrap();
call(cb);
}

相关内容

  • 没有找到相关文章

最新更新