gogoland

Cannot use 'func(c Container) interface{} { return &MyService{} }' (type func(c Container) interface{}) as type ContainerFunc


I'm trying to understand how to build a service container to go. Though it's not a recommended process still I want to learn something from it.

But after structuring code like this image below.

enter image description here

I'm getting an error message like this below. enter image description here

Source code:

    package unit
    
    import (
        "testing"
        "github.com/stretchr/testify/assert"
        framework "../../framework"
    )
    type ContainerFunc func(container Container) interface{}
    
    type MyService struct {
        Name string
    }
    
    type Container framework.Container
    
    func TestContainerSingleton(t *testing.T) {
    
        c := framework.New()
    
        assert.False(t, c.Has("test.service.name"))
    
        assert.Equal(t, []string{}, c.GetKeys())
    
        c.Set("my.service", func(c Container) interface{} {
            return &MyService{}
        })
    
    }

container.go

package framework

import (
    "fmt"
    "log"
    "reflect"
    "sync"
)

// Container is for containing any services
type Container interface {
    Set(name string, f ContainerFunc)
    Has(name string) bool
    Get(name string) interface{}
    GetKeys() []string
    Fill(name string, ds interface{})
    Extend(name string, f ExtenderFunc)
}

// ContainerFunc func will generate interfaces based on need
type ContainerFunc func(container Container) interface{}

// ExtenderFunc means interface
type ExtenderFunc interface{}

type container struct {
    values   map[string]ContainerFunc
    extends  map[string][]reflect.Value
    services map[string]interface{}
    mtx      *sync.RWMutex
}

//New method initialize a new Container and returns it.
func New() Container {
    return &container{
        services: make(map[string]interface{}),
        values:   make(map[string]ContainerFunc),
        extends:  make(map[string][]reflect.Value),
        mtx:      &sync.RWMutex{},
    }
}

/*
In Set method storing the container
*/
func (c *container) Set(name string, f ContainerFunc) {
    c.mtx.Lock()
    defer c.mtx.Unlock()

    //Checking if the service is in the format, if service is there throw a panic
    if _, ok := c.services[name]; ok {
        log.Panic("Can not overwrite initialized service")
    }
    c.values[name] = f
    fmt.Printf("%s service has been registered", name)
}

/*
Has Checks if the name exists in map and return boolean value
it first locks the c.values for reading then checks and at last using
defer it release the lock
*/
func (c *container) Has(name string) bool {
    //applying read lock on value map
    c.mtx.RLock()
    //releasing lock after return call
    defer c.mtx.RUnlock()
    // Checking if the value exists or not, and put the boolean value into "ok" variable
    if _, ok := c.values[name]; ok {
        return true
    }

    return false
}

func (c *container) Get(name string) interface{} {
    //locks reading from c.values
    c.mtx.RLock()
    _, ok := c.values[name]
    //unlocking it after reading and put the boolean value into the ok variable
    c.mtx.RUnlock()
    //if its false panic a error
    if !ok {
        panic(fmt.Sprintf("The service does not exist: %s", name))
    }

    //if panic is not triggered
    //read lock services from reading
    c.mtx.RLock()
    //check if the name is in the services
    _, ok = c.services[name]
    //read unlock the service map
    c.mtx.RUnlock()

    // the ok (boolean) is false type define container as c in "v" variable
    if !ok {
        v := c.values[name](c)

        c.mtx.RLock()
        c.services[name] = v
        c.mtx.RUnlock()
        // it iterates over the extends map and ....
        if extends, ok := c.extends[name]; ok {
            for _, extend := range extends {
                //creating an slice of reflect value
                result := extend.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(c)})
                c.mtx.Lock()
                c.services[name] = result[0].Interface()
                c.mtx.Unlock()
            }
        }
    }

    c.mtx.RLock()
    defer c.mtx.RUnlock()
    return c.services[name]
}

// Fill Returns error if anything happens
func (c *container) Fill(name string, dst interface{}) {
    // getting the struct
    obj := c.Get(name)
    // added element the dst interface using fill funciton
    if err := fill(obj, dst); err != nil {
        log.Panic(err)
    }
}

//Extend mainly have control over the c.extends , it is appending a callback function
func (c *container) Extend(name string, f ExtenderFunc) {
    c.mtx.Lock()
    defer c.mtx.Unlock()

    if _, ok := c.services[name]; ok {
        log.Panic("Cannnot extend initialized service")
    }

    if _, ok := c.values[name]; !ok {
        log.Panicf("Cannot extend %s service", name)
    }

    c.extends[name] = append(c.extends[name], reflect.ValueOf(f))
}

// Get keys mainly creates a empty map , fill the map with all the value with string
//by appending and return the map
func (c *container) GetKeys() []string {
    c.mtx.RLock()
    defer c.mtx.RUnlock()

    keys := make([]string, 0)

    for k := range c.values {
        keys = append(keys, k)
    }
    return keys
}

//fill method just add an element to the dest interface (which is being injected)
func fill(src, dest interface{}) (err error) {
    defer func() {
        if r := recover(); r != nil {
            d := reflect.TypeOf(dest)
            s := reflect.TypeOf(src)
            err = fmt.Errorf("The fill destination should be a pointer to a %s , but you used a %s", s, d)
        }
    }()
    reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
    return err
}

the error message I'm getting, Any idea how to solve this?:

Cannot use 'func(c Container) interface{} { return &MyService{} }' (type func(c Container) interface{}) as type ContainerFunc


Solution

  • If I attempt to rebuild your situation (with Go 1.13) by constructing my own Framework from guesses—it would help if you showed us what is in Framework—the closest I can get is this:

    ./unit.go:25:22: cannot use func literal (type func(Container) interface {})
    as type framework.ContainerFunc in argument to c.Set
    

    which is due to this line:

    type Container framework.Container
    

    Changing it to:

    type Container = framework.Container
    

    so that this is an alias for the existing type makes the code build. (It would probably be better to just use framework.Container where appropriate, rather than defining your own unit.Container type.)

    (If your IDE is dropping the framework. prefix from framework.Container, and otherwise shortening the Go compiler's error messages here, that would explain it.)