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

implement interfaces at runtime [was: methods declared in the interpreter are not visible in compiled code] #45

Open
corebreaker opened this issue Dec 3, 2018 · 3 comments
Assignees
Labels

Comments

@corebreaker
Copy link

corebreaker commented Dec 3, 2018

Hello,

I'd like to know where is the problem with this piece of code:

package main

import(
    "fmt"
    "bytes"
    "github.com/cosmos72/gomacro/fast"
)

const source = `type T struct{}
func (*T) F() {}
`

func main(){
    src := bytes.NewReader([]byte(source))

    it := fast.New()
    if _, err := it.EvalReader(src); err != nil { panic(err) }

    v, _ := it.Eval1("new(T)")

    fmt.Println(v.NumMethod())
}

It prints «0» (zéro). So that's mean that with a type with methods when a value is created from this type, it don't have the methods.

@cosmos72
Copy link
Owner

cosmos72 commented Dec 6, 2018

You took the red pill :)

It's a bit hidden in doc/features-and-limitations.md, that says:

  • named types created by interpreted code are emulated. When the interpreter is asked to create for example type Pair struct { A, B int }, it actually creates the unnamed type struct { A, B int }. Everything works as it should within the interpreter, but extracting the struct and using it in compiled code reveals the difference.

This implies, although it's not written explicitly, that methods declared in interpreted code are emulated too, because it's not possible to declare methods on unnamed types.

If the type you created stays in the interpreter, it works as expected. But if you pass it to compiled code (and reflect.Value.NumMethod is compiled code), the differences become visible.

There is no easy solution, because gomacro is a regular (although complex) Go program that uses reflection to create types, values, functions etc.: current Go reflect package does not support declaring new named types, interfaces, and methods, so gomacro emulates them.

@corebreaker
Copy link
Author

What a pity ! Create a type with methods, instanciate and import it at runtime is my goal, but i believe that Go can do that. That's the first step to create Proxy instances from interfaces.

So as it's impossible with actual Go state, i close this issue.

@cosmos72 cosmos72 reopened this Dec 13, 2018
@cosmos72
Copy link
Owner

cosmos72 commented Dec 13, 2018

If your goal is to implement pre-existing interfaces at runtime, that's actually easier.
Gomacro supports that, and you can do it too in your code, even without an interpreter.

  1. using gomacro. Thanks to your question I just found a bug for methods on pointers. But it works if you define methods on values, as shown below:
package main

import (
	"fmt"
	"io"

	"github.com/cosmos72/gomacro/fast"
)

const source = `
import "io"

type T struct { }
func (t T) Read(p []byte) (n int, err error) {
	println("read  called")
	return 0, nil
}
func (t T) Write(p []byte) (n int, err error) {
	println("write called")
	return 0, nil
}
`

func main() {
	it := fast.New()

	it.Eval(source)
	// you must explicitly instruct the interpreter to convert T{}
	// into a io.ReadWriter
	v, _ := it.Eval1("io.ReadWriter(T{})")

	rw := v.Interface().(io.ReadWriter)

	rw.Read(nil)
	rw.Write(nil)
	fmt.Printf("%+v\n", rw)
}
  1. do-it-yourself easy case: you know at compile-time which interfaces you want to implement at runtime. For example, let's say you want to implement io.ReadWriter at runtime, which has two methods:
interface {
  Read(p []byte) (n int, err error)
  Write(p []byte) (n int, err error)
}

Then you prepare a proxy struct that implements io.ReadWriter and compile it in your code:

type Proxy_io_ReadWriter struct {
  Obj interface{} // if needed by ReadFunc and WriteFunc below
  ReadFunc func(Obj interface{}, buf []byte) (n int, err error) // this field is a function
  WriteFunc func(Obj interface{}, buf []byte) (n int, err error) // this too
}
// declare the Read method matching io.ReadWriter signature 
func (P *Proxy_io_ReadWriter) Read(buf []byte) (n int, err error) {
  // forward the call to ReadFunc
  return P.ReadFunc(P.Obj, buf);
}
// declare the Write method matching io.ReadWriter signature 
func (P *Proxy_io_ReadWriter) Writebuf []byte) (n int, err error) {
  // forward the call to WriteFunc
  return P.WriteFunc(P.Obj, buf);
}

At runtime, it's just a matter of creating a Proxy_io_ReadWriter, setting its fields, and storing it in a io.ReadWriter - depending on your needs, you can do it either directly, or using the reflect package. The code looks like this:

// something that returns a func(Obj interface{}, buf []byte) (n int, err error),
// maybe even by using reflect.MakeFunc() or a Go interpreter
readFunc := makeReadFunc()
// as above, something that returns a func(Obj interface{}, buf []byte) (n int, err error)
writeFunc := makeWriteFunc()
obj := makeObj() // if needed by readFunc and writeFunc
p := Proxy_io_ReadWriter{
  Obj: obj,
  ReadFunc: readFunc,
  WriteFunc: writeFunc,
}
var rw io.ReadWriter = &p
  1. do-it-yourself difficult case: you know only at runtime which interfaces you want to implement. Then you need to create a .go source file containing the same proxy declaration as case 1) above, then compile it to a shared library with go build -buildmode=plugin, then load it with the plugin package (only supported on Linux and Mac OS X), finally create and fill the proxy object as in case 1) above, but using reflection.

For completeness, gomacro uses both techniques 1) and 2) internally.

@cosmos72 cosmos72 changed the title Instanciate a type with methods don't have methods implement interfaces at runtime [was: methods declared in the intepreter are not visible in compiled code] Dec 13, 2018
@cosmos72 cosmos72 changed the title implement interfaces at runtime [was: methods declared in the intepreter are not visible in compiled code] implement interfaces at runtime [was: methods declared in the interpreter are not visible in compiled code] Dec 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants