2 tough problems I met

I am writing a program using crate iced. However, type conversions between elements can be quite confusing.
How can I fix the code to pass compilation?

The first problem

I want to load a new image when Message::ImageChanged is called.

Here is my code:

enum Message {
    Loaded(Result<State, Error>),
    ImageChanged(u32),
}
pub struct State {
    // ...
}

impl Application for Memories {
    fn update(&mut self, message: Message) -> Command<Message> {
        match self {
            Memories::Loading => Command::none(),
            Memories::Loaded(state) => {
                match state.stage {
                    Stage::ChoosingCharacter(chosen) => match message {
                        Message::ImageChanged(next) => {
                            *self = Memories::Loading;
                            chosen.on_image = next;
                            let img_path = state
                                .idxtable
                                .get("image");
                            Command::perform(async { // line 110
                                chosen.image = Some(
                                    state
                                        .get_image(img_path.to_string())
                                        .await
                                        .expect("Cannot get image.")
                                );
                                state
                            }, Message::Loaded) // line 118
                        }

which generated the following error:

error[E0631]: type mismatch in function arguments
   --> src\main.rs:118:32
    |
63  |     Loaded(Result<State, Error>),
    |     ------ found signature defined here
...
110 |                             Command::perform(async {
    |                             ---------------- required by a bound introduced by this call
...
118 |                             }, Message::Loaded)
    |                                ^^^^^^^^^^^^^^^ expected due to this
    |
    = note: expected function signature `fn(&mut State) -> _`
               found function signature `fn(Result<State, Error>) -> _`
note: required by a bound in `iced::Command::<T>::perform`
   --> D:\Scoop\persist\rustup-msvc\.cargo\registry\src\github.com-1ecc6299db9ec823\iced_native-0.7.0\src\command.rs:39:17
    |
39  |         f: impl FnOnce(T) -> A + 'static + MaybeSend,
    |                 ^^^^^^^^^^^^^^ required by this bound in `iced::Command::<T>::perform`

This is the simliar code in pokedex:

The second problem

Just like the pokemon (line 77) example which indirectly calls image::viewer(iced::widget::image::Handle) -> Viewer<iced::widget::image::Handle> to display image by a handle, I tried to create an element with an image handle but received such type conversion errors.

Code from the example:

My code with wrong type conversions:

enum Stage {
    ChoosingCharacter(ChoosingState),
    ShowingPlots,
    Graduated,
}

pub struct ChoosingState {
    chosen_character: u32,
    on_image: u32,
    image: Option<image::Handle>,
}

impl Application for Memories {
// --snip--
    fn view(&self) -> Element<Message> {
        match self {
            Memories::Loaded(state) => {
                match state.stage {
                    Stage::ChoosingCharacter(chosen) => row![
                        match chosen.image {
                            Some(handle) => image::viewer(handle),  // line 149
                            None => text("Not able to load image.").size(40)
                        }, // line 151
                        Self::show_name("Class 1")
                    ]

which generated the following error:

error[E0308]: `match` arms have incompatible types
   --> src\main.rs:150:37
    |
148 | /                         match chosen.image {
149 | |                             Some(handle) => image::viewer(handle),
    | |                                             --------------------- this is found to be of type `Viewer<iced::widget::image::Handle>`
150 | |                             None => text("Not able to load image.").size(40)
    | |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Viewer`, found struct `iced_native::widget::text::Text`
151 | |                         },
    | |_________________________- `match` arms have incompatible types
    |
    = note: expected struct `Viewer<iced::widget::image::Handle>`
               found struct `iced_native::widget::text::Text<'_, _>`

New error such as error[E0283]: type annotations needed will be produced if I add into() to it.


If you can help me solve this problem, could you also help me clarify the topological relationships between iced::widget::image::Viewer, iced::Element, and iced::Renderer?

Thanks a lot.

This one looks like a problem on line 117 where the state is returned. It needs to be type Result<State, Error> but is &mut State (see the error message with "expected" and "found" types).

It isn't clear without seeing the rest of the code, but one way to address this might be cloning the state for the async block and returning it in Ok. It requires that State implements Clone, which I don't know is acceptable for your application.

let state = state.clone();
Command::perform(async {
    chosen.image = Some(
        state
            .get_image(img_path.to_string())
            .await
            .expect("Cannot get image.")
    );
    Ok(state)
}, Message::Loaded)

Viewer and Text are different types. Both arms of the surrounding match statement need to produce the same type [1]. It looks like you want something like this:

Stage::ChoosingCharacter(chosen) => match chosen.image {
    Some(handle) => row![
        image::viewer(handle),
        Self::show_name("Class 1"),
    ],
    None => row![
        text("Not able to load image.").size(40),
        Self::show_name("Class 1"),
    ],
},

Now both arms return a Row.

Another option that may work is using the From implementation on Element:

Stage::ChoosingCharacter(chosen) => row![
    match chosen.image {
        Some(handle) => Element::from(image::viewer(handle)),
        None => Element::from(text("Not able to load image.").size(40)),
    },
    Self::show_name("Class 1")
]

Now both arms return Element.


  1. Or diverge. ↩︎

@parasyte
Thanks very much. I've solved the second problem with your help. However, new errors about lifetime occured.

enum Memories {
    Loading,
    Loaded(State),
}

#[derive(Clone, Debug)]
pub struct State {
    stage: Stage,
    idxtable: toml::value::Table,
    client: reqwest::Client,
    url_prefix: String,
    storage: String,
}

#[derive(Clone, Debug)]
enum Stage {
    ChoosingCharacter(ChoosingState),
    ShowingPlots,
}

#[derive(Clone, Debug, Default)]
pub struct ChoosingState {
    on_image: u32,
    image: Option<image::Handle>,
}

#[derive(Debug)]
enum Message {
    Loaded(Result<State, Error>),
    ImageChanged(u32),
}

impl Application for Memories {
    fn update(&mut self, message: Message) -> Command<Message> {
        match self {
            Memories::Loading => Command::none(),
            Memories::Loaded(state) => {
                match &state.stage {
                    Stage::ChoosingCharacter(chosen) => match message {
                        Message::ImageChanged(next) => {
                            let (mut chosen, state) = (chosen.clone(), state.clone());
                            let img_path = state
                                .idxtable
                                .get("image");
                            *self = Memories::Loading;
                            Command::perform(
                                async move {
                                    chosen.on_image = next;
                                    chosen.image = Some(
                                        state
                                            .get_image(img_path.to_string())
                                            .await
                                            .expect("Cannot get image."),
                                    );
                                    Ok(state)
                                },
                                Message::Loaded,
                            )
                        }

Here is the error:

error[E0597]: `state.idxtable` does not live long enough
   --> src\main.rs:104:52
    |
104 |                                       let img_path = state
    |  ____________________________________________________^
105 | |                                         .idxtable
106 | |                                         .get("image")
    | |                                                     ^
    | |                                                     |
    | |_____________________________________________________borrowed value does not live long enough
    |                                                       argument requires that `state.idxtable` is borrowed for `'static`        
...
127 |                           }
    |                           - `state.idxtable` dropped here while still borrowed

error[E0505]: cannot move out of `state` because it is borrowed
   --> src\main.rs:115:44
    |
104 |   ...                       let img_path = state
    |  __________________________________________-
105 | | ...                           .idxtable
106 | | ...                           .get("image")
    | |                                           -
    | |                                           |
    | |___________________________________________borrow of `state.idxtable` occurs here
    |                                             argument requires that `state.idxtable` is borrowed for `'static`
...
115 |   ...                   async move {
    |  __________________________________^
116 | | ...                       chosen.on_image = next;
117 | | ...                       chosen.image = Some(
118 | | ...                           state
    | |                               ----- move occurs due to use in generator
...   |
123 | | ...                       Ok(state)
124 | | ...                   },
    | |_______________________^ move out of `state` occurs here

Is there any other way to share the variable state except creating a 'static variable to solve the problem?

You don't need 'static variable, but 'static type. Difference is sticking: 'i8 is, of course, 'static type, but language where every byte-sizes variable have to be 'static would be insane.

Usual way to solve the issue is via Arc<Mutex<State>>. That's 'static variable, you can 'clone it and access State when needed. Just look on the examples in the official documentation.

P.S. If you need to hold the Mutex across await-points then you need tokio::Mutex, not normal, synchronous one.

No need to borrow in this situation. Convert the path to an owned string before moving it into the async block.

Thanks a million!