Yew and the html macro: lifetimes problem

Hi, I am building a small webapp in yew and have a problem with lifetimes in the html macro that I do not fully understand. The code is here (Branch "router"!): Files · router · andreas.pfeil / Fairris · GitLab

I have several components. The Model Component is the "main" one, and it contains a List of structs PidInfo, which happen to be also components, but they do not have a link or props or messages. So I instantiate them like a notmal rust struct. I see in the console that this generates warnings, but thats a different problem. I can just make them normal structs with a view function I think.

The PidInfo structs render basically as a routerbutton which redirects to a Route which shall display more information. And this is where the problems start.

struct Model {
    pub link: ComponentLink<Self>,
    pub known_pids: Vec<PidInfo>,
}

// PID means persistent identifier, they are unique IDs to thos items.
pub struct PidInfo { pub pid: String, ... }
impl Default for PidInfo {...}

impl Component for PidInfo {
    ...
    fn view(&self) -> Html {
        let pid = self.pid.clone();
        html! {
            <RouterButton<AppRoute> route=AppRoute::Details(pid) classes="piditem">
                <p>{ self.pid.clone() }</p>
                <p>{ self.description.clone() }</p>
                <p>{ self.status.clone() }</p>
            </RouterButton<AppRoute>>
        }
    }
}

#[derive(Switch, Debug, Clone)]
pub enum AppRoute {
    #[to = "/create"]
    CreateFdo,
    #[to = "/fdo/{id}"]
    Details(String),
    #[to = "/search"]
    Search,
    #[to = "/"]
    Index,
}

The router handling was basically adapted from one of the yew examples and is integrated in the Models view function within the html macro:

impl Component for Model {
    type Message = Msg;
    type Properties = ();
    ...
    fn view(&self) -> Html {
        html! {
            <div id="everything">  // this can be reduced to <></>
                <div id="sidebar" class="maincolumns">
                    <div id="pidbuttons">
                        <RouterButton<AppRoute> route=AppRoute::CreateFdo>{ "+" }</RouterButton<AppRoute>>
                        <RouterButton<AppRoute> route=AppRoute::Search>{ "search" }</RouterButton<AppRoute>>
                        <button onclick=self.link.callback(|_| Msg::Remove)>{ "-" }</button>  // TODO this should create a callback to remove a pid.
                    </div>
                    <div id="workspace" class="scroll-vertical">
                        { for self.known_pids.iter().map(|pidinfo| pidinfo.render()) }
                    </div>
                </div>
                <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
                        match switch {
                            AppRoute::CreateFdo => html!{<CreateComponent/>},
                            AppRoute::Details(pid) => {
                                html!{}
                                //(method1) self.view_pid_details(pid) will not work: lifetime error
                                //(dummy) html!{<DetailsComponent/>}, will work, but not receive the Item or a link
                                //(method2) html!{<DetailsComponent model_link=self.link.clone() item=self.get_item(pid) />}, will result in the same lifetime error.
                            },
                            AppRoute::Search => html!{<SearchComponent/>},
                            AppRoute::Index => html!{<CreateComponent/>},
                        }
                    })
                />
            </div>
        }
    }
}

Please note that method1 does not render the PidInfo component but uses just it's content. This is the dummy implementetion of this detailsview:

    pub fn view_pid_details(&self, pid: String) -> Html {
        let pid = self.find_pidinfo_by_string(pid).pid;
        html!{
            <div id="content" class="maincolumns scroll-vertical">
                <div class="image-placeholder"><p>{ "IMAGE" }</p></div>
                <div class="textblock">
                    <p>{ format!("{}: {}", "PID:", pid) }</p>
                    <p>{ "Print PID Record here." }</p>
                    ...
                </div>
                <div class="fdo-actions"><p>{ "Placeholder for Action Buttons here." }</p></div>
                <div class="action-placeholder"><p>{ "Placeholder for action visualization. This could be i.e. viewing raw metadata, visualizations, or the possibility to update your FDO." }</p></div>
            </div>
        }
    }

But even if the method does just nothing, consumes no data etc, the compiler will complain.

If I try the commented out sections method1 or method2, the resulting error looks like this:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
   --> src/lib.rs:87:63
    |
87  |                   <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
    |  _______________________________________________________________^
88  | |                         match switch {
89  | |                             AppRoute::CreateFdo => html!{<CreateComponent/>},
90  | |                             AppRoute::Details(pid) => {
...   |
96  | |                         }
97  | |                     })
    | |_____________________^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 74:5...
   --> src/lib.rs:74:5
    |
74  | /     fn view(&self) -> Html {
75  | |         html! {
76  | |             <div id="everything">
77  | |                 <div id="sidebar" class="maincolumns">
...   |
100 | |         }
101 | |     }
    | |_____^
note: ...so that the types are compatible
   --> src/lib.rs:87:63
    |
87  |                   <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
    |  _______________________________________________________________^
88  | |                         match switch {
89  | |                             AppRoute::CreateFdo => html!{<CreateComponent/>},
90  | |                             AppRoute::Details(pid) => {
...   |
96  | |                         }
97  | |                     })
    | |_____________________^
    = note: expected  `&&Model`
               found  `&&Model`
    = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/lib.rs:87:63: 97:22 self:&&Model]` will meet its required lifetime bounds
   --> src/lib.rs:87:48
    |
87  |                 <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
    |                                                ^^^^^^^^^^^^^^
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

I am not even sure to which lifetimes this refers, I just have some theories. But I failed to express those ideas in lifetimes annotations. I implemented a fn do_nothing(&self) {} method for Model, and even this is a problem for the compiler if I call it instead of the empty html!{} macro. Whenever I try to do something with self in there, this happens. I tried cloning or borrowing the pid and nothing seemed to work. The versions of my dependencies are:

[dependencies]
wasm-bindgen = "^0.2"
yew = "0.17"
yew-router = "0.14"

wee_alloc = "0.4"
log = "0.4"
wasm-logger = "0.2"

How can I use self to do something in the routers match statement? Is there a better/less lifetime-error prone way to do the routing in yew? I think method1 should be the easiest as I do not have to copy over the Item to another component (compared to method2).

Thank you.

Problem solved. Basically I had to "wrap" the data I want to pass into an Rc so I can clone it. Thanks to the people on the yew discord server :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.