Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type assertion fails #19

Closed
darmie opened this issue Aug 30, 2023 · 8 comments
Closed

Type assertion fails #19

darmie opened this issue Aug 30, 2023 · 8 comments

Comments

@darmie
Copy link

darmie commented Aug 30, 2023

Unwrapping b.as_ref() is not guaranteed, and fails sometimes when you pass an interface{} value into an ffi function.

let meta = b.as_ref().unwrap().0;

I wonder what the general behavior would be if you applied this as a fix

let want_meta = want_meta.as_metadata();
if b.is_none() {
   return Ok((want_meta.zero(metas, gcc), false));
}
let meta = b.as_ref().unwrap().0;
@oxfeeefeee
Copy link
Owner

Made a few changes, see the CL above

@darmie
Copy link
Author

darmie commented Aug 31, 2023

So I am sending data from FFI function to the runtime as an interface, this data could be any underlying type. The test scenario I worked with sends a map[string]interface{} which will be received as an interface{} type in Goscript code, it gets trickier by the time I try to access the map's elements , that was when I started getting the type assertion errors.

Based on your comment in the commit above:

if you pass None to clone_mb_from, the returned interface won't behave like
a normal interface. you cannot cast it or call it's methods.

Consider this code:

package main

import "my_ffi"

func main() {
	var in = my_ffi.GetData()
	// get inputs
	data, ok := in.(map[string]interface{})
	assert(ok)

	left, _ := data["left"].(int) // <<== Type assertion error starts here
	right, _ := data["right"].(int)
}

How do I know what to pass into clone_mb_from ?

This is how I convert serde_json::Value types to GosValue

fn to_go_value(ctx: &FfiCtx, value: Value) -> GosValue {
    match value.clone() {
        ....
        Value::Object(m) => {
            ctx.new_map(m.into_iter().map(|(k, v)|{
                let _val = FfiCtx::new_interface(to_go_value(ctx, v.clone()), None); // <<== I have no idea what to use instead of `None`
                (FfiCtx::new_string(&k), _val)
            }).collect())
        }
    }
}

oxfeeefeee added a commit that referenced this issue Aug 31, 2023
@oxfeeefeee
Copy link
Owner

oxfeeefeee commented Aug 31, 2023

So I am sending data from FFI function to the runtime as an interface, this data could be any underlying type. The test scenario I worked with sends a map[string]interface{} which will be received as an interface{} type in Goscript code, it gets trickier by the time I try to access the map's elements , that was when I started getting the type assertion errors.

Based on your comment in the commit above:

if you pass None to clone_mb_from, the returned interface won't behave like
a normal interface. you cannot cast it or call it's methods.

Consider this code:

package main

import "my_ffi"

func main() {
	var in = my_ffi.GetData()
	// get inputs
	data, ok := in.(map[string]interface{})
	assert(ok)

	left, _ := data["left"].(int) // <<== Type assertion error starts here
	right, _ := data["right"].(int)
}

How do I know what to pass into clone_mb_from ?

This is how I convert serde_json::Value types to GosValue

fn to_go_value(ctx: &FfiCtx, value: Value) -> GosValue {
    match value.clone() {
        ....
        Value::Object(m) => {
            ctx.new_map(m.into_iter().map(|(k, v)|{
                let _val = FfiCtx::new_interface(to_go_value(ctx, v.clone()), None); // <<== I have no idea what to use instead of `None`
                (FfiCtx::new_string(&k), _val)
            }).collect())
        }
    }
}

I realized the previous API requires too much understanding of the inner workings, I changed it so that you need to pass the metas of the interface and the value. Which are still hard to use, will figure out some improments.

For now, for your case, you can get the interface meta via PrimitiveMeta.empty_iface, and Meta::new_map() for the value. For examples, see
https://github.com/oxfeeefeee/goscript/blob/master/std/os/file.gos
https://github.com/oxfeeefeee/goscript/blob/master/engine/src/std/os.rs

It looks like you are trying to convert arbitrary serde_json::Value to an Goscript interface, that's not possible. What you can do is to create a FFI via which your Goscript code can access the json::Value.
What you are trying to do is basically asking Goscript to be a dynamic language, but currently it cannot. I will consider to add a special "Dynamic" type to meet requirements like yours.

@darmie
Copy link
Author

darmie commented Aug 31, 2023

it looks like you are trying to convert arbitrary serde_json::Value to an Goscript interface, that's not possible. What you can do is to create a FFI via which your Goscript code can access the json::Value.
What you are trying to do is basically asking Goscript to be a dynamic language, but currently it cannot. I will consider to add a special "Dynamic" type to meet requirements like yours.

I have a function that checks the internal type of the json value,

fn map_go_types(v: Value) -> ValueType {
    match v {
        Value::Null => ValueType::Void,
        Value::Bool(_) => ValueType::Bool,
        Value::Number(n) => {
            if n.is_f64() {
                ValueType::Float64
            } else if n.is_i64() {
                ValueType::Int64
            } else {
                ValueType::Uint64
            }
        }
        Value::String(_) => ValueType::String,
        Value::Array(_) => ValueType::Array,
        Value::Object(_) => ValueType::Map,
    }
}

For object and array, it does a recursive walk through their element values and check their types.

For example, an array

 Value::Array(a) => {
    let data = a
                .iter()
                .map(|d| to_go_value(ctx, d.clone())) // <<== calling to_go_value to convert element values recursively
                .collect::<Vec<_>>();

            ctx.new_array(data, map_go_types(value))  
}

This is why I rely on "interface{}" types on the Go side of things, to afford the flexibility of casting the value to the necessary type on demand. This is currently possible with the Go runtime.
This should work successfully in Goscript if I send an interface{} from an FFI function
https://go.dev/play/p/gCIT99MbBNz

I am assuming for Goscript, that interface{} type should be a union of all types similar to how ValueData works

@darmie
Copy link
Author

darmie commented Aug 31, 2023

For now, for your case, you can get the interface meta via PrimitiveMeta.empty_iface, and Meta::new_map() for the value. For examples, see
https://github.com/oxfeeefeee/goscript/blob/master/std/os/file.gos
https://github.com/oxfeeefeee/goscript/blob/master/engine/src/std/os.rs

Closely watching these files, I have now created an UnsafePtr and FFI module specifically for accessing dynamic data.

Looking forward to an official Dynamic API though :)

@oxfeeefeee
Copy link
Owner

https://go.dev/play/p/gCIT99MbBNz

This should work in Goscript as well.
It's different from creating interfaces from FFI, as the compiler gets all the type info and store it in the interface.
But it does't work for now because it triggers another unrelated codegen bug :), working on it.

oxfeeefeee added a commit that referenced this issue Aug 31, 2023
@darmie
Copy link
Author

darmie commented Aug 31, 2023

I am excited to see Goscript fully working, it's currently an experimental embedded runtime for my project zflow.

So this is how I am currently handling the data, based on your suggestion:
https://github.com/zflow-dev/zflow/blob/064ff51f0c24c4a94fdad9f155f38a46dcddf3ea/zflow_runtime/src/go/utils.rs#L140

And the goscript example looks like this:

package main

import "zflow"

func main() {
	// get inputs
	var in = zflow.Inputs()
	if !in.IsNil() {
		data, ok := in.Map()
		if !ok {
			panic("could not read inputs")
		}
		left, _ := data["left"].I64()
		right, _ := data["right"].I64()
		// some calculation
		total := left + right

		packet := map[string]int64{"sum": total}
		zflow.Send(packet)
	}
}

@oxfeeefeee
Copy link
Owner

I am excited to see Goscript fully working, it's currently an experimental embedded runtime for my project zflow.

Glad to hear! Your feedback has been very helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants