rune icon indicating copy to clipboard operation
rune copied to clipboard

Can't construct struct with named field

Open JohnnnyWang opened this issue 3 years ago • 1 comments

Hi, I can't construct struct like this:

let action = SendAction {
                   seq: 1,
                   target_id: "123",
                   headers: [],
                   body_bytes: Bytes::new(),
                   wait_strategy: RuneWaitStrategy::Ignore
              };

its will cause an error:

 missing runtime information for variant with hash 0xe49bb8eb2c9a12e7 (at inst 40) 

I know I must be doing something wrong, but I don't know how to debug and fix it.

Here is my rune code


#[cfg(test)]
mod test {
    // rune 0.11.0 from git master
    // rune-modules 0.11.0
    // rust version: 1.61.0
    // windows

    use std::sync::Arc;
    use std::time::Duration;

    use rune::runtime::{SyncFunction, VmError};
    use rune::termcolor::{ColorChoice, StandardStream};
    use rune::{Context, ContextError, Diagnostics, FromValue, Module, Vm};

    #[derive(Debug, Clone, rune::Any)]
    pub enum RuneWaitStrategy {
        #[rune(constructor)]
        Ignore,
        #[rune(constructor)]
        Must(#[rune(get)] Dur),
        #[rune(constructor)]
        AsyncOnError,
        #[rune(constructor)]
        AsyncAlways,
    }

    #[derive(Debug, Clone, rune::Any)]
    pub struct SendAction {
        #[rune(get, set)]
        pub seq: u64,
        #[rune(get, set)]
        pub target_id: String,
        #[rune(get, set)]
        pub headers: Vec<(String, String)>,
        #[rune(get, set)]
        pub body_bytes: rune::runtime::Bytes,
        #[rune(get, set)]
        pub wait_strategy: RuneWaitStrategy,
    }

    #[derive(Debug, Clone, rune::Any)]
    pub struct Dur {
        pub inner: Duration,
    }

    fn module() -> Result<Module, ContextError> {
        let mut module = Module::with_crate("m_action");
        module.ty::<SendAction>()?;
        module.ty::<RuneWaitStrategy>()?;
        module.ty::<Dur>()?;

        Ok(module)
    }

    #[test]
    pub fn test() {
        let mut sources = rune::sources! {
            entry => {
                use m_action::*;
                use std::bytes::*;
                fn next(seq, events) {
                   let action = SendAction{
                        seq: 1,
                        target_id: "123",
                        headers: [],
                        body_bytes: Bytes::new(),
                        wait_strategy: RuneWaitStrategy::Ignore

                    };

                    // but this works
                    //let action = SendAction::new(...)

                    action
                }
                pub fn main() {
                    next
                }
            }
        };

        let m = module().unwrap();
        let mut context = Context::with_default_modules().unwrap();

        context.install(&m).unwrap();
        let runtime = Arc::new(context.runtime());
        let mut diagnostics = Diagnostics::new();
        let result = rune::prepare(&mut sources)
            .with_context(&context)
            .with_diagnostics(&mut diagnostics)
            .build();

        if !diagnostics.is_empty() {
            let mut writer = StandardStream::stderr(ColorChoice::Always);
            diagnostics.emit(&mut writer, &sources).unwrap();
        }

        let unit = result.unwrap();

        let mut vm = Vm::new(runtime, Arc::new(unit));
        let controller_f = SyncFunction::from_value(vm.call(&["main"], ()).unwrap()).unwrap();

        // error here: MissingRtti hash: xxx
        let mut action: SendAction = controller_f.call((1, 2)).unwarp();
    }
}


JohnnnyWang avatar Jun 02 '22 14:06 JohnnnyWang

Thanks for the report!

udoprog avatar Jun 03 '22 06:06 udoprog

So I think the issue lies in try_lookup_meta. It forgets to add the meta to the UnitBuilder, so ultimately the compiled Unit is missing the run-time type information (RTTI) when the VM tries to construct the object later on.

All that's needed is to include that extra line, like we do in the index_meta call:

self.unit.insert_meta(location.as_spanned(), &meta, self.pool, self.inner)?;
self.insert_meta(meta.clone()).with_span(location.as_spanned())?;

Then something like this will work fine:

 pub fn main() {
     let external = External {
         first: 1,
         second: "two",
     };

     external
}

But this fails if you want to extract the value from the VM:

let output = vm.call(["main"], ())?;
let output: External = rune::from_value(output)?;
println!("{:?}", output);
Error: Expected `Any` type, but found `External`

Because the from_value implementation is expecting a Value::Any, when it's actually a Value::Struct. That means the VM is actually treating the External Rust struct as a Rune struct, just reusing the field names. You can debug the Value and see the correct result, or coerce it into a Shared<Struct> and interact with it as an object using .get("first").

To get an actual External type from the VM, I think we'd need to push an instance of External onto the stack as an Value::Any, which would require associating a constructor function with the type like enum variants have now. See my draft PR (#589) for an attempt at that.

jbdutton avatar Jul 26 '23 17:07 jbdutton