My Go courses are discounted for the next few weeks to help out anyone who may need or want access to them. I'm also going to try to help out anyone who can't afford a course, and I will be writing posts about working from home over the next week in an attempt to help anyone new to WFH. Read more here .

Go Experience Report: Interfaces with Methods that Return Themselves

I want to describe a scenario where there currently isn’t a good solution in Go (at least that I am aware of). Let’s say you have something like the template.Template type with methods like Template.Funcs:

type Template struct { * parse . Tree // contains filtered or unexported fields } func ( t * Template ) Funcs ( funcMap FuncMap ) * Template

If we wanted to use an interface for this type, it is impossible to express in Go now. At first you would think that you could use an interface like this:

type Funcser interface { Funcs ( FuncMap ) Funcser }

But if you try it out in Go it won’t work. While our template.Template type appears to match this definition, template.Funcs method returns the *template.Template type, NOT the Funcser type, and that means it wont implement this interface.

Truthfully, I’m not certain that every implementation of generics would solve this issue, nor do I feel that generics are required to solve the issue, but if generics are added to Go I’d love to see an implementation that somehow makes this situation a little better.

Know of a better solution? If you happen to know of a better solution to this problem let me know. I may just be ignorant to better solutions and if so I’d love to hear about them 🙂

Followup - A More Concrete Example

It was pointed out (and I agreed) that this post is a little short on details. I’m going to try to expand a bit here in order to give a better example of why this matters and how it can affect a real application.

According to the Experience Reports wiki:

The best experience reports tell: (1) what you wanted to do, (2) what you actually did, and (3) why that wasn’t great, illustrating those by real concrete examples, ideally from production use.

I’ll start with these three points, as I think they are enough to really illustrate the experience.

(1) what you wanted to do

Using GORM is where this comes up most frequently, but it has happened with other packages in the past. In GORM you typically create queries by chaining something like this:

db . Where ( "role = ?" , "admin" ). Or ( "role = ?" , "super_admin" ). Find ( & users )

My issue is that the second I introduce this code into my application, I can’t easily test it because I can’t replace the *gorm.DB type with an interface. I now need either a live database connection for my tests, or I need to write a fairly large wrapper around GORM. That is, I can’t write code like this:

type WhereOrFinder interface { func Where ( ... ) WhereOrFinder func Or ( ... ) WhereOrFinder func Find ( ... ) * gorm . DB } func admins ( db WhereOrFinder ) [] User { var users [] Users err := db . Where ( "role = ?" , "admin" ). Or ( "role = ?" , "super_admin" ). Find ( & users ). Error return users , err }

And then test it with a mock.

Now before I move on, I’m guessing that most people are going to stop here and say, “Woah, you returned *gorm.DB on that Find method! Why not return it for all of the methods?” This doens’t work because then the chained methods would no longer be using the mock. I can create a mock like this:

type mockDB struct {} func ( m mockDB ) Where ( ... ) mockDB { return m } func ( m mockDB ) Or ( ... ) mockDB { return m } func ( m mockDB ) Find ( ... ) * gorm . DB { return & gorm . DB { Error : someError } }

And it will work because my code doesn’t leave the mock until the final result is returned, but if I were to instead create a mock like this:

type WhereOrFinder interface { func Where ( ... ) * gorm . DB func Or ( ... ) * gorm . DB func Find ( ... ) * gorm . DB } type mockDB struct {} func ( m mockDB ) Where ( ... ) * gorm . DB { // If I return a gorm.DB here the rest of my code will interact // with a REAL gorm db, not my mock! } func ( m mockDB ) Or ( ... ) * gorm . DB { return ... } func ( m mockDB ) Find ( ... ) * gorm . DB { return & gorm . DB { Error : someError } }

You could make the argument that occasionally I will want to hit a live DB in my tests and I agree, but I don’t want to do that for literally every single test that uses gorm.DB . It slows things down, and it means I need to do some sort of DB “reset” (or rollback) after each test to make sure I start with a clean slate. In an ideal world I would instead be testing with a mock.

(2) what you actually did

One of two things:

Option 1 - just use sqlite3 in the tests. No mocking or anything else, but it works even if it is slower.

Option 2 - Write a wrapper around the type so that I can more easily mock it. Here is an example of one someone else wrote for GORM - https://github.com/jinzhu/gorm/pull/1424/files#diff-48a8c4d93131a93810f1ac27bf60a10d

I tend to end up going with Option 1 most often because it is clear what is going on and that we use GORM, whereas option 2 requires me to figure out all the methods we might use w/ GORM, make sure other devs know to add new ones they need if they aren’t in the mock, and so on.

(3) why that wasn’t great, illustrating those by real concrete examples, ideally from production use.

Interfaces are, in my opinion, great in Go because they are easy to use and because someone creating a type doesn’t have to go out of their way to define all the interfaces it implements. That is, I don’t have to write something like:

type Dog implements < Sortable , Stringer , ... >

Duck typing and the simplicity it brings to Go’s interfaces are (again imo) what allows the “accept interfaces, return structs” mantra to work.

Having to create a wrapper or simply not use an interface isn’t great because it is directly contradictory to this point. When we have a type that returns itself in a method, package developers must explicitly define the interface themselves, or users of the type will need to create an often large wrapper to enable that behavior. You can see this in the example I linked above for a GORM wrapper - the code there is required to overwrite every single method we might use in the original gorm.DB type and update them all to return a specific interface instead. It works, but it just never felt ideal.

It is probably also worth noting that this is a big part of the reason why I tend to push back when someone wants to use method chaining in Go instead of another approach like functional options.