Hey guys this code is part of a mock client, mock server interaction. I am having trouble understanding context.
Here I explicitly "associate" my tracker interface with context using 'WithValue' and then inject it into my request using WithContext. But when I check if my request's context contains the tracker interface I am returned the error "This context should contain a tracker" . What is it about context and WithValue that I am just not understanding?
var tracker Tracker
ctx := context.WithValue(context.Background(), contextKey, tracker)
req := httptest.NewRequest("GET", "localhost:12345/test", nil)
req.Header.Add(HEADER)
req = req.WithContext(ctx)
_, ok := ctx.Value(contextKey).(Tracker)
if !ok {
log.Fatal("1: This context should contain a tracker")
}
Tracker
is an interface, and it's not set to anything, so it's nil
. So, it can't change nil
to a Tracker
, so it fails.
The problem isn't that tracker
is nil, the problem is that it doesn't contain a value of a concrete type that implements Tracker
. The value of tracker
could be nil, and this would work, but it has to be a "typed" nil.
The type assertion fails, because type assertions and type switches only work when the instance being asserted has a concrete type. A value can be of a concrete type by declaration , assignment, type assertion , or a type switch case.
When you do the type assertion ctx.Value(contextKey).(Tracker)
There's (conceptually) a 2-step process to this:
ctx.Value(contextKey)
's concrete type.Tracker
.Here's a playground example that hopefully illustrates this better: https://play.golang.org/p/ojYzLObkisd
The DoAny()
function in there is emulating what is happening when you put your tracker
into context and then try to retrieve it with the type assertion.
I know this is just a basic example, but it's not very good practice to use var something SomeInterfaceType
, even if you do assign it a value. You should use concrete types for that. Or even better, just use type inference, and you won't have to worry about it. For example:
type Foo interface {
DoFoo() string
}
type MyFoo struct {}
func (f *MyFoo) DoFoo() string {
return "some foo value"
}
// not so good
var f Foo = new(MyFoo)
// good
var f *MyFoo = new(MyFoo)
// better
f := new(MyFoo)
This leverages the fact that interfaces are implicit in Go, and leads to much more obvious and maintainable code, especially in larger projects. Declaring a variable with an interface type is essentially making your variable poloymorphic, but the real power of interfaces isn't polymorphism. The real power is in using them as "functional requirements" for your package's exposed functions/methods. One rule that illustrates this really well is "Accept interfaces, return structs".
EDIT: I've edited my original answer to correct some mistakes and make some improvements. I also would like to answer a follow-up question from the OP:
what I did not understand is that unless you have an object that utilizes that tracker's methods your interface is nil. Is this correct thinking?
I think what you're trying to say is correct, but the words used aren't quite right. First, there are no objects in Go, as it is not object-oriented. Where in OOP languages, objects hold instances of types, Go uses variables and constants to hold instances of types. So the concept exists, but not by the same name. So your tracker
variable will be an instance of a type that satisfies your Tracker
interface, but its value with be nil
unless you assign it a non-nil instance of a type that satisfies the Tracker
interface.