0

我试图弄清楚如何从 yew 示例中使这个 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,它将编译但请求不会运行。‍♂️</p>

4

1 回答 1

3

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_xis a Fn,而不是 a FnOnce,因此在使用时不会消耗它,也就是说,您可以根据需要多次调用它。

另一个没有回调的解决方法是使用 anOption并通过使用Option::take. 这将其替换为None,并且从借用检查器的角度来看,该值继续存在。但是,您需要 a 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);
}

更新到最后一个选项

RefCell当一个简单的就行时不要使用Cell。并且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);
}
于 2019-08-19T15:07:37.900 回答