0

I'm implementing material 3 from scratch with web_sys and writing some adapters for different frameworks among them yew.

I have implemented a basic structure for buttons something like this to use like this.

This works fine on web_sys but I'm trying to write the yew adapter and I'm having problems by casting between yew::Callback<web_sys::MouseEvent> to FnMut(web_sys::MouseEvent).

My adapter looks like this...

this is the definition for button in my core module

And this is the definition in my adapter crate:

In adapter props takes both InnerData and ComponentOpts in one structure

// the properties in yew adapter
#[derive(PartialEq, Properties)]
pub struct ElevatedButtonProps {
    pub label: AttrValue,

    #[prop_or(false)]
    pub disable: bool,

    #[prop_or_default]
    pub icon: Option<AttrValue>,

    #[prop_or_default]
    pub onclick: Option<Callback<MouseEvent>>,
}

And the functional component in yew, which is the adapter per se, takes the properties passed to it and with them creates the button with my "core" library and returns it as a virtual node of yew.

/// this is my core library https://gitlab.com/material-rs/material_you_rs/-/tree/agnostic-api/crates/core?ref_type=heads
use core::components::buttons::elevated::{self, ElevatedButtonData, ElevatedButtonOpts};
use web_sys::{HtmlElement, MouseEvent};
use yew::{function_component, virtual_dom::VNode, AttrValue, Callback, Html, Properties};


#[function_component(ElevatedButton)]
pub fn elevated_button(props: &ElevatedButtonProps) -> Html {
    let ElevatedButtonProps {
        label,
        disable,
        icon,
        onclick,
    } = props;

    let icon = if let Some(icon) = icon {
        Some(icon.to_string())
    } else {
        None
    };

    let inner = if let Some(onclick) = onclick {
        // problem is here when i'm trying to cast
        let cb = move |e: MouseEvent| {
            onclick.clone().emit(e.clone());
        };

        ElevatedButtonData {
            label: label.to_string(),
            icon,
            onclick: Some(Box::new(cb.clone())),
        }
    } else {
        ElevatedButtonData {
            label: label.to_string(),
            icon,
            onclick: None,
        }
    };

    let opts = ElevatedButtonOpts { disable: *disable };

    let button: HtmlElement = elevated::ElevatedButton::new(inner, opts);

    VNode::VRef(button.into())
}

But this is not working...

The compiler tells me that the yew::Callback must outlive 'static, but at the same time the macro annotation function_component does not allow me lifetimes annotations.

20 | pub fn elevated_button(props: &ElevatedButtonProps) -> Html {
   |                               - let's call the lifetime of this reference `'1`
...
42 |             onclick: Some(Box::new(cb.clone())),
   |                           ^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`

does anyone have any idea how I can deal with this?

edit:

"core" is my library where I'm implementing the components only over web_sys and in the adapter I'm "instantiating" it in a comfortable way depending on the framework of the shift

al3x
  • 589
  • 1
  • 4
  • 16
  • 1
    Can you please make a [**minimal** reproducible example](/help/minimal-reproducible-example)? There is a lot of noise here. – cafce25 Jul 30 '23 at 04:50
  • 1
    There is no module `components` in [`core`](https://doc.rust-lang.org/stable/core/index.html) can you please elaborate what you mean? (Or preferably if possible remove the unnecessary parts and include definitions of the necessary ones.) – cafce25 Jul 30 '23 at 04:56
  • I'm sorry, core is my component library, I just updated my question, I hope it is more readable now. – al3x Jul 30 '23 at 05:24
  • 1
    Please don't link to external resources for additional context, if it's relevant include definitions, if not remove the `use` statement and their use. – cafce25 Jul 30 '23 at 05:31
  • 3
    Try to `onclick.clone()` **outside** of the `move || {}`. By calling `.clone()` inside, you move first, then clone. Which makes the entire cloning pointless. Although I of course can't verify, because your example is too convoluted. Please provide a [mre]. The `clone` on the `MouseEvent` can be removed entirely, it's pretty pointless. You already own the value, you just simple create a second one and discard the first one. Use the first one directly. – Finomnis Jul 30 '23 at 06:07

1 Answers1

2

As your example is too complex and incomplete to be reproduced, I will assume that the following is your actual example:

pub fn convert_callback(
    onclick: &yew::Callback<web_sys::MouseEvent>,
) -> impl FnMut(web_sys::MouseEvent) {
    let cb = move |e: web_sys::MouseEvent| {
        onclick.clone().emit(e.clone());
    };
    cb
}
error[E0700]: hidden type for `impl FnMut(MouseEvent)` captures lifetime that does not appear in bounds
 --> src\lib.rs:7:5
  |
2 |     onclick: &yew::Callback<web_sys::MouseEvent>,
  |              ----------------------------------- hidden type `[closure@src\lib.rs:4:14: 4:43]` captures the anonymous lifetime defined here
3 | ) -> impl FnMut(web_sys::MouseEvent) {
  |      ------------------------------- opaque type defined here
...
7 |     cb
  |     ^^
  |
help: to declare that `impl FnMut(MouseEvent)` captures `'_`, you can add an explicit `'_` lifetime bound
  |
3 | ) -> impl FnMut(web_sys::MouseEvent) + '_ {
  |                                      ++++

The reason this fails is because you clone() the onclick object inside of the closure, moving it first before you clone it.

Also, as a minor remark, cloning e is pretty pointless as you already own it. It just creates a copy, then discards the original object. Use the original object directly instead.

Then, it should compile:

pub fn convert_callback(
    onclick: &yew::Callback<web_sys::MouseEvent>,
) -> impl FnMut(web_sys::MouseEvent) {
    let onclick = onclick.clone();
    let cb = move |e: web_sys::MouseEvent| {
        onclick.emit(e);
    };
    cb
}

You can also put those two actions into a nested scope, if you don't want to leak the cloned onclick object into the outer scope:

pub fn convert_callback(
    onclick: &yew::Callback<web_sys::MouseEvent>,
) -> impl FnMut(web_sys::MouseEvent) {
    let cb = {
        let onclick = onclick.clone();
        move |e: web_sys::MouseEvent| {
            onclick.emit(e);
        }
    };
    cb
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • This works, really thanks, I was stuck here a lot hours trying it. I was even trying to manually implement `FnMut` for a new type, I hadn't noticed the error. – al3x Jul 30 '23 at 06:26
  • 1
    @al3x Be glad you have the borrow checker, in other languages (especially C++) this would have been a silent error that would cause undefined behavior and would be very hard to find. – Finomnis Jul 30 '23 at 06:28
  • Indeed! It's one of the reasons I love rust! Sometimes it can be a bit painful, but it's worth it. – al3x Jul 30 '23 at 06:33