DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
435 stars 59 forks source link

Problems with using rquickjs in a function with generics #208

Closed jasontheiler closed 10 months ago

jasontheiler commented 10 months ago

Hi everyone,

I'm currently trying to create a Code enumerator, that abstracts different implementations of various methods for different programming languages (like JavaScript and Lua). Now, this is probably not an issue with the library itself, but I'm having problems with the lifetime of the Ctx when I use rquickjs inside a function that takes a generic for either a parameter or a return value.

I always run into the error that borrowed data escapes outside of the closure at some point or another. It must have something to do with the lifetime of the IntoJs and FromJs traits, which I'd set in the method's signature. But I can't figure out what I'd have to change to make this work. Maybe the design of rquickjs simply doesn't allow for this kind of usage... I'm a little out of my depth here and it would be awesome if someone could help me out here!

pub enum Code {
    Js(String),
    Lua(String),
}

impl Code {
    pub fn eval_bool<G>(
        &self,
        globals: impl IntoIterator<Item = (&'static str, G)>,
    ) -> anyhow::Result<bool>
    where
        G: rquickjs::IntoJs<'static>,
    {
        match self {
            Self::Js(code) => {
                let rt = rquickjs::Runtime::new()?;
                let ctx = rquickjs::Context::full(&rt)?;
                ctx.with(|ctx| -> anyhow::Result<_> {
                    let ctx_globals = ctx.globals(); // ERROR: borrowed data escapes outside of closure - requirement occurs because of the type `rquickjs::Ctx<'_>`, which makes the generic argument `'_` invariant
                    for (key, val) in globals {
                        ctx_globals.set(key, val)?;
                    }
                    let val = ctx.eval::<rquickjs::Value, _>(format!("(()=>{code})()"))?;
                    let val = val
                        .get::<rquickjs::convert::Coerced<_>>()
                        .map(|val| val.0)?;
                    Ok(val)
                })
            }
           Self::Lua(code) => todo!(),
        }
    }
}
DelSkayn commented 10 months ago

The trait bound G: rquickjs::IntoJs<'static> makes sure that a type will only be able to be converted into a Value with a lifetime of 'static. Since converting to a Value with a specific lifetime requires a Ctx object with the exact same lifetime this bound requires Ctx to have a 'static lifetime inside the closure. This can never be the case thus resulting in the compile error.

Instead of 'static try using a HRTB: G: for<'a> rquickjs::IntoJs<'a>

jasontheiler commented 10 months ago

Thank you! That just did the trick. Lifetime is a concept I still struggle with sometimes and I never had a case before where I had to use a higher-ranked trait bound, so I didn't even think of it... Guess I have some catching up to do. Thank you again.