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

Allow to specify an interface when providing a constructor #197

Closed
alessandrozucca opened this issue Jul 6, 2018 · 25 comments
Closed

Allow to specify an interface when providing a constructor #197

alessandrozucca opened this issue Jul 6, 2018 · 25 comments

Comments

@alessandrozucca
Copy link
Contributor

alessandrozucca commented Jul 6, 2018

Update (by @glibsm)

The addition of dig.As ProvideOption has been agreed on.


Currently dig only uses a constructors that returns exactly what the function expects.

If the function accepts an interface, and we don't inject the constructor that returns that interface we get this error

panic: missing dependencies for function "main".main.func1 (..../main.go:29): type main.Fooer is not in the container, did you mean to use *main.Foo?

Take the following example

type Foo struct{}

func NewFoo() *Foo {
	return &Foo{}
}

func (b *Foo) Hello() {
	fmt.Println("hello")
}

type Fooer interface {
	Hello()
}

func main() {
	di := dig.New()
	err := di.Provide(NewFoo)
	if err != nil {
		panic(err)
	}
	err = di.Invoke(func(f Fooer) {
		fmt.Printf("f: %+v", f)
	})
	if err != nil {
		panic(err)
	}
}

So to make it work it needs to have a constructor function such as:

func NewFoo() Fooer {
	return &Foo{}
}

which is basically making us declare that Foo implements Fooer
Would it be feasible to change the library so that it can workout itself if the struct implements the interface? [edit] as clarified in the comments this suggestion won't work

Or alternatively, add a new ProvideOption which allow us to specify which concrete implementation to inject for a given interface?

@abhinav
Copy link
Collaborator

abhinav commented Jul 11, 2018

Hello! This is a good question and a recurring problem.

Although it's possible for dig to map structs to interfaces, we explicitly
chose not to do this because of the ambiguity it introduces. Without
explicitly saying "Use Foo for Fooer", you can have a situation where another
object just happens to implement Fooer, but you don't want it to be used as
such in your application. You end up with no guarantee that the correct object
will be picked, and there's no good way to say "Use only this object as Fooer"
outside of what you already have to do with dig.

That said, this is still something we would like to simplify in the future. A
clean API for doing so hasn't presented itself yet and finding one hasn't been
a priority for us because the current approach is only a little inconvenient.

We'll keep an eye out for a design that solves this well, but until then the
extra little boilerplate isn't too much to deal with.


A ProvideOption that allows you to specify the concrete implementation would
be more boilerplate than func(f *Foo) Fooer { return f } because Go doesn't
have a syntax to reference types as objects. What I mean is, you can't do,

UseImplementation(*Foo)
UseImplementation(Foo)

You'd have to provide the type information through another concrete type that
you can pass around like a func, a value, or a pointer.

UseImplementation(func() *Foo {})
UseImplementation(Foo{})
UseImplementation((*Foo)(nil))

IMO that's more confusing than just,

func(f *Foo) Fooer { return f }

@alessandrozucca
Copy link
Contributor Author

Hi @abhinav

They are both valid points so i'll just keep doing something along the lines of providing func(f *Foo) Fooer { return f }

Thanks for the clarification and open sourcing this library

I'll close the issue

@abhinav
Copy link
Collaborator

abhinav commented Jul 25, 2018

Re-opening because another possible API came up. We can pass references to
interfaces in a more readable way with new(Fooer) so we can probably do
something along the lines of,

func NewFoo() *Foo

c.Provide(NewFoo, dig.Cast(new(Fooer)))

We'll want to figure out naming to make it explicit that this isn't an
arbitrary cast, only interfaces. Maybe dig.As(...).

@abhinav abhinav reopened this Jul 25, 2018
@alessandrozucca
Copy link
Contributor Author

To me dig.As sounds nicer than dig.Cast

Or you could use dig.Bind(...) same name as the one used by go-cloud/wire ?

@alessandrozucca
Copy link
Contributor Author

alessandrozucca commented Aug 16, 2018

If this solution is implemented in combination with the one suggested in #181

It will make the following code shorter

type FooParams struct {
	dig.In
	Foo *Foo `name:"foo"`
}
type FooResult struct {
	dig.Out
	Fooer Fooer `name:"foo"`
}
func NewFooer(p FooParams) FooResult {
	return FooResult{
		Fooer: p.Foo,
	}
}

by using

c.Provide(NewFoo, dig.As(Fooer), dig.Name("foo"))

@abhinav
Copy link
Collaborator

abhinav commented Aug 16, 2018

dig.As(new(Fooer)) but yeah, agreed. We'll explore adding this probably in
a few weeks depending on priorities.

Until then, we suggest continuing to use the explicit func-based cast --
unless you'd like to take a stab at implementing this.

@glibsm
Copy link
Collaborator

glibsm commented Aug 16, 2018

Going back to one of the original comments:

which is basically making us declare that Foo implements Fooer

Yes, it's true that in Go interfaces are implicit.

However, in the case of a dependency injection container it's a good thing that you have to declare who implements what since there can never be an ambiguity.

Consider the following example:

type A struct {}
func (a A) String() { } // implements fmt.Stringer

type B struct {}
func (b B) String() { } // implements fmt.Stringer

// fx.Provide(NewA, NewB)...
func New(s fmt.Stringer) {} // constructor requiring a fmt.Stringer

In the scenario above, there is an ambiguity. Both objects implement a stringer (it's easy for the container to check that). However, which object is to be returned in that case?

An error would have to be thrown in this case about too may stringers in the container, which is not desired since those two objects should be able to coexist just fine.

@alessandrozucca
Copy link
Contributor Author

@glibsm yeah, @abhinav already clarified that point in his response #197 (comment)

I'll update the title of the issue.

@alessandrozucca alessandrozucca changed the title When an invoked function requires an interface use any constructor that implements it Allow to specify an interface when providing a constructor Aug 17, 2018
@rssathe
Copy link

rssathe commented Dec 12, 2018

@abhinav updates on this?

@abhinav
Copy link
Collaborator

abhinav commented Dec 12, 2018

Hey @rssathe. I think we have a design we agree upon: dig.As(new(Fooer)) as
a ProvideOption (CC @glibsm). Unfortunately, we don't have the bandwidth to
implement this feature at this time, and it hasn't been a priority because the
manual casting process suffices for our needs for now.

@glibsm
Copy link
Collaborator

glibsm commented Dec 26, 2018

@abhinav dig.As sounds good to me.

Perhaps also as: tag for dig.Out? Otherwise I don't see an easy way that Fx can consume dig.As. How would this look in Fx world?

@abhinav
Copy link
Collaborator

abhinav commented Dec 26, 2018

The as: tag might be problematic because we'll be string matching on types
names and packages.

For Fx, now that we have fx.Annotated, maybe it can be a simple mapping
from an fx.Annotated field to a Dig option?

fx.Provide(fx.Annotated{
    Target: NewFoo,
    As: new(Bar),
})

@glibsm
Copy link
Collaborator

glibsm commented Dec 27, 2018

Ah, that's right. The fx.Annotated wrapper should provide a good solution to this.

@nikkomiu
Copy link

Would it be possible to either:

  • Change the signature of the Provide to take in ...interface{} as a "second" param where you could specify zero or more interfaces that map to that single struct? This would keep the Provide method backwards compatible.
  • Add a secondary function like ProvideNamed that takes the struct as the first argument and the interface it maps to as a second argument? (This would correlate to more of a .NET Core type container injection system)

@glibsm
Copy link
Collaborator

glibsm commented Jan 16, 2019

Change the signature of the Provide to take in ...interface{}

That is not an option for two reasons:

  1. It's an API breaking change, which would require a dig 2.0 version. We are very careful with following the semver practices
  2. It would get in the way of all other usages that don't require a cast. Lest say in 5% of the cases, a cast is required (as far as I can tell, it's pretty rare). Any other dig usage would now incur an additional parameter.

Add a secondary function like ProvideNamed

There is a very good post by Dave Cheney on this topic: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

Essentially, we don't want to redefine a new method any time some option is introduce (like casting to another interface). We intentionally left room in the API signature of Provide to allow us to introduce new options and features without API breaking changes.

@nikkomiu
Copy link

nikkomiu commented Jan 16, 2019

The first option wouldn't be a breaking change because it allows zero or more parameters to be passed into the function. Not one or more. So you also wouldn't be required to pass it into the function.

https://golang.org/ref/spec#Passing_arguments_to_..._parameters

@glibsm
Copy link
Collaborator

glibsm commented Jan 16, 2019

The first option wouldn't be a breaking change because it allows zero or more parameters to be passed into the function. Not one or more. So you also wouldn't be required to pass it into the function.

I'm not sure I follow.

Provide already defines one set of variadic arguments. You can't have two variadic arguments.
func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error
https://godoc.org/go.uber.org/dig#Container.Provide

Please illustrate through a code sample what you are suggesting and it's affect on the API.

@nikkomiu
Copy link

nikkomiu commented Jan 16, 2019

Ah, I didn't see that there was already a ...ProvideOption on there. That wouldn't have worked anyway 😞...at least not very nicely.

Would it be possible to then add to the ProvideOption interface to allow for interface mapping?

@glibsm
Copy link
Collaborator

glibsm commented Jan 16, 2019

Would it be possible to then add to the ProvideOption interface to allow for interface mapping?

Yep. dig.As ProvideOption is the currently agreed on way forward.

See the discussion earlier in this issue #197 (comment)

alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 7, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 7, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 7, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 7, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 7, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 13, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
alessandrozucca pushed a commit to alessandrozucca/dig that referenced this issue Mar 15, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes uber-go#197
abhinav pushed a commit that referenced this issue Mar 27, 2019
This adds a dig.As option which allows the caller to specify a
constructor and a list of interfaces that the constructed struct
implements.

Closes #197
abhinav added a commit that referenced this issue Nov 14, 2019
This reverts #233 until we resolve discussion on it so that we don't
accidentally release it.

Reopens #197
@abhinav abhinav reopened this Nov 14, 2019
abhinav pushed a commit that referenced this issue Nov 14, 2019
This brings back #233.

Per #235 (review),
the issues we need to resolve are,

1. `dig.As` seems to indicate that it's a total override of the provided
   type. The way it currently works is it appends other interfaces on
   top of whatever the constructor already returns
2. semantics of `dig.Provide(func New() (Foo, io.Reader, error), dig.As(new(io.Writer)`.
   It currently fails due to inability to case reader to writer, but
   we'd want some extra validation here. Perhaps `dig.As` is only
   supported for a single type, error tuple?

Closes #197
abhinav added a commit that referenced this issue Nov 14, 2019
This reverts #233 until we resolve discussion on it so that we don't
accidentally release it.

Reopens #197
abhinav pushed a commit that referenced this issue Nov 14, 2019
This brings back #233.

Per #235 (review),
the issues we need to resolve are,

1. `dig.As` seems to indicate that it's a total override of the provided
   type. The way it currently works is it appends other interfaces on
   top of whatever the constructor already returns
2. semantics of `dig.Provide(func New() (Foo, io.Reader, error), dig.As(new(io.Writer)`.
   It currently fails due to inability to case reader to writer, but
   we'd want some extra validation here. Perhaps `dig.As` is only
   supported for a single type, error tuple?

Closes #197
@phamtai97
Copy link

Hi all,
Has this feature been released yet? If not, what is the current solution?

@un000
Copy link

un000 commented Oct 15, 2020

Hi all,
Has this feature been released yet? If not, what is the current solution?

Manually constructing an object with a lambda.

@zenthangplus
Copy link

Hi all, any update?

@toukoubun
Copy link

toukoubun commented Mar 29, 2021

Same problem, and here is my solution.

Different from google/wire, dig can't bind interface with specific value type. But dig can deal with interface and interface
So, we can just write a bind function that receives the value and returns the interface

// receives value and returns interface
func BindFooer(f *Foo) Fooer {
	return f
}

di.Provide(NewFoo)
di.Provide(BindFooer)

di.Invoke(func(f Fooer) {
		fmt.Printf("f: %+v", f)
})

Of course, I know, writing the bind function every time is too much trouble, so can we bind interface with specific value type simply just like google/wire ? For example:

// code from google/wire
wire.Bind(new(Fooer), new(*Foo))
wire.Bind(new(Barer), new(*Bar))
wire.Bind(new(Bazer), new(*Baz))

The answer is YES, here is the source code of bind() function and useage

// bind Fooer with Foo*
di.Provide(bind(new(Fooer), new(Foo)))
// bind Fooer with Foo
di.Provide(bind(new(Fooer), Foo{}))
// if you created Fooer already
var fooer Fooer
di.Provide(bind(&fooer, Foo{}))


func bind(interfaceValue interface{}, structValue interface{}) interface{} {
	interfaceType := reflect.TypeOf(interfaceValue)
	if interfaceType.Kind() == reflect.Ptr {
		interfaceType = interfaceType.Elem()
	}
	if interfaceType.Kind() != reflect.Interface {
		panic("the type of parameter 'interfaceValue' should be interface")
	}

	in := []reflect.Type{reflect.TypeOf(structValue)}
	out := []reflect.Type{interfaceType}
	funcType := reflect.FuncOf(in, out, false)

	return reflect.MakeFunc(funcType, func(args []reflect.Value) (results []reflect.Value) {
		return args
	}).Interface()
}

It works fine for me, good luck

@umi0410
Copy link

umi0410 commented Sep 6, 2021

Binding also works for me. Thank you.

sywhang pushed a commit to sywhang/dig that referenced this issue Sep 15, 2021
This brings back uber-go#233.

Per uber-go#235 (review),
the issues we need to resolve are,

1. `dig.As` seems to indicate that it's a total override of the provided
   type. The way it currently works is it appends other interfaces on
   top of whatever the constructor already returns
2. semantics of `dig.Provide(func New() (Foo, io.Reader, error), dig.As(new(io.Writer)`.
   It currently fails due to inability to case reader to writer, but
   we'd want some extra validation here. Perhaps `dig.As` is only
   supported for a single type, error tuple?

Closes uber-go#197
@sywhang
Copy link
Contributor

sywhang commented Sep 17, 2021

#293 added dig.As option. We'll try to release this shortly.

@sywhang sywhang closed this as completed Sep 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.