我正在尝试弄清楚如何使紫杉示例中的此 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_x
是Fn
,而不是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);
}