risor icon indicating copy to clipboard operation
risor copied to clipboard

type error: unable to call method on type (check pointer receiver)

Open SolarLune opened this issue 11 months ago • 4 comments

Hello - I'm sure this is already a known issue, but I don't see any active issues about this. It's currently impossible to set a value or call a method on a non-pointer struct. That's a bit of a problem if you're working with an external Golang library that doesn't directly deal with pointers; not sure if there's anything that can be done about this.

For example, the following is impossible:

package main

import (
	"context"
	"fmt"

	"github.com/risor-io/risor"
)

type Object struct {
	A int
}

func (o Object) Set(other Object) Object {
	o.A = other.A
	return o
}

type Creator struct{}

func (c *Creator) NewObject() Object {
	return Object{}
}

func main() {

	src := `
	obj := Creator.NewObject()
	obj2 := Creator.NewObject()
	obj3 := obj.Set(obj2)
	`

	_, err := risor.Eval(context.Background(), src, risor.WithGlobal("Creator", &Creator{}))
	if err != nil {
		fmt.Println(err)
	}

}

SolarLune avatar Jan 07 '25 06:01 SolarLune

Hi @SolarLune, I think this is resolved on main now. Want to try it out?

Related - https://github.com/risor-io/risor/pull/315

There hasn't been a tagged release yet, so you'll need to build Risor yourself.

myzie avatar Mar 14 '25 15:03 myzie

Hello - no, I think the issue still remains. And I'm sorry; I think my test code didn't exhibit the problem I was having. Here's a simpler test case that does show the problem:

type Object struct {
	A int
}

func (o *Object) Modify() {
	o.A = 71
}

func main() {

	script := `
	Object.A = 10 // Doesn't work with the error: [ type error: cannot set field A ]
	Object.Modify() // Doesn't work with the error: [ reflect: Call using main.Object as type *main.Object ]
	`

	fmt.Println(risor.Eval(context.Background(), script, risor.WithGlobal("Object", Object{})))

}

Making the Object{} reference a pointer then allows you to set struct fields directly or call struct-modifying methods as a workaround.

SolarLune avatar Mar 15 '25 03:03 SolarLune

The key here is that the Risor object.Proxy should wrap a pointer to a struct. If you control creation of that object, then you should do this or the equivalent instead: risor.WithGlobal("Object", &Object{})).

However I understand you may not directly control the code.

In your situation, is the object you're working with returned as a value via a field access on a different object that is proxied?

myzie avatar Apr 23 '25 14:04 myzie

Hello - sorry again for the confusing discussion; it's been awhile since I was testing Risor out, so please forgive me for being confusing. In addition, in my previous post, I made the mistake of trying to call Object.Modify() when the object reference wasn't a pointer - that wouldn't work in Go either, so that made the issue I was running into more opaque, for which I apologize. To give some context, I was (and still am) trying out different scripting languages for use with embedding into games for some simple scripting.


So first off, on the latest version of Risor, I can call functions on non-pointer structs, which is great.

However, there's still a slightly related issue which is that it's impossible to set a field on a non-pointer struct.

In your situation, is the object you're working with returned as a value via a field access on a different object that is proxied?

Essentially, I have my own external gamedev and 3D rendering library, Tetra3D, and there's a Vector struct type in it, as can be seen here. My approach to using it with Risor was to create an interoperation layer - a struct that has functions that is accessible in Risor by passing it into the scripting engine via risor.WithGlobal(), like so:

type Engine struct{}

func (e *Engine) NewVector(x, y, z float32) tetra3d.Vector3 {
	return tetra3d.NewVector3(x, y, z)
}

I believe one of the issues I was running into when going with this approach is that you can't set fields directly on non-pointer structs (so this doesn't work):


package main

import (
	"context"
	"fmt"

	"github.com/risor-io/risor"
	"github.com/solarlune/tetra3d"
)

type Engine struct{}

func (e *Engine) NewVector(x, y, z float32) tetra3d.Vector3 {
	return tetra3d.NewVector3(x, y, z)
}

func main() {

	src := `
	
	a := Engine.NewVector(4,5,6)
	b := Engine.NewVector(1,2,3)
	c := a.Add(b) // This works now, which is great!
	c.X = 15 // However, this fails with "type error: cannot set field X"
	`

	_, err := risor.Eval(context.Background(), src, risor.WithGlobal("Engine", &Engine{}))
	if err != nil {
		panic(err)
	}

}

I have the ability to create pointer versions of Vectors in my library, but it may still be nice to consider how to solve this for situations where such an option isn't available and an external library uses plain structs and allows access via struct fields. Could Risor be modified to create a pointer when necessary to access and modify struct fields?

SolarLune avatar Apr 27 '25 14:04 SolarLune

@SolarLune - want to try out this change? https://github.com/risor-io/risor/pull/371

I think it might address the problem.

I'm also curious whether creating dedicated Risor wrapper objects for some of your Tetra3d types might be preferable, since you'll have greater control and it will be more efficient since it wouldn't rely on reflection so much at runtime.

Are you still considering using Risor in Tetra3d?

myzie avatar May 04 '25 15:05 myzie

Hello - this PR does seem to resolve the issue I was having; thank you very much!

Creating dedicated wrappers would probably be better for performance, yeah - I might end up going that route.

Yeah, I might use Risor with my use-case (a game that uses Tetra3D) - I think Risor executes faster than JS through goja generally in my tests (with goja being the interpreter I'm using currently), so it would be nice to gain some performance.

SolarLune avatar May 08 '25 03:05 SolarLune