Thursday, July 30, 2020

Go and Interfaces

I've restarted my adventures in Go (golang). Based on the experience that I've had in the meantime, I have a better appreciation of many of the language features.

Specifically, I've become a fan of how Go handles interfaces compared to C#. (Interfaces in C# have become a bit of a complicated mess. I've written a series of articles about the recent changes (A Closer Look at C# 8 Interfaces) and have done some recent user group talks on the changes that come out over 2 hours long (recording of the Tulsa .NET User Group)).
I find that the Go minimalist approach to interfaces can make things easier to code.
So, let's look at an example that I recently worked through.

Go Interfaces

Before looking at the example, let's look at how interfaces work in Go.

Here is the "io.Writer" interface that is part of the Go io package (docs: io.Writer):

type Writer interface {
    Write(p []byte) (n int, err error)
}

This declares the "Writer" interface which has one method: "Write".

The "Write" method takes a byte array as a parameter, and returns an int (the number of bytes written) as well as an error (in case any errors occur). As a side note, the "int, error" return type is a common pattern in Go.

The interesting bit (and where things differ from C#) is how types implement this interface. Any type that wants to implement the "io.Writer" interface just needs to have the "Write" method itself. It does not need to declare itself as an "io.Writer", it just has to have the method.

This means that any type with a "Write" method that matches this signature is automatically an "io.Writer".

Let's see this in action.

Lissajous Curve

My example code comes from The Go Programming Language by Alan A. A. Donovan and Brian W. Kernighan (Amazon link). One of the early tutorials is to create a Lissajous curve. Here's an gif showing what that is:


The code is a bit of math that gets output to an animated gif. I have this code on GitHub (jeremybytes/go-programming-language), specifically, we'll look at the "ch1/lissajous" project. The code is all in a single file, "main.go" (link on Github: main.go).

Here's the code for creating the gif (starting at line 44 of main.go):


This has the constants collapsed, but you can check the full code for details.

There are 2 bits I want to point out here: First, the "lissajous" function takes an "io.Writer" as a parameter. Finally, the "gif.EncodeAll" method at the bottom uses the "io.Writer" parameter to produce the output.

Using the Lissajous Function

We use the lissajous function in the "main" function (the "main" function gets executed when we run the Go program).

The most basic call is on line 41 (from main.go):

lissajous(os.Stdout)

This passes "os.Stdout" to the "lissajous" function. The result is that the gif is written to Standard Out (which is the console if we just run this as a console application).

Writing a gif to the console is not very useful, but we can pipe the output of our program to a file like this:


.\lissajous.exe > out.gif

This will pipe the output of the program to a file named "out.gif".

A few notes here. First, the ".\lissajous.exe" command has a backslash. That's because I'm running this on Windows 10. Next, this command doesn't actually work in PowerShell on Windows 10. The "out.gif" file is created, but it's not an actual gif (meaning, nothing can display it). I'm sure this is PowerShell weirdness (I show how I got around this in just a bit). When I run this command on macOS (with a slash instead of a backslash), it works just fine. The "out.gif" file is a valid animated gif.

A Web Server

One of the cool things about Go is that we can spin up a web server with just a few lines of code. Here is a bit of code that we can add to the "main" method to serve up the animated gif (starting on line 27 of main.go):


This sets up an HTTP handler that calls the "lissajous" method. On the second line, note that the handler is a function that takes an "http.ResponseWriter" as a parameter. This type implements the "io.Writer" interface, so all we need to do is pass it to the "lissajous" method.

We can run the application with the "web" parameter like this:


.\lissajous.exe web

Then, we can navigate a browser to "http://localhost:8000" to get this result:


Pretty cool. (Okay, so it's cooler if you do it yourself because this screenshot doesn't show the animation.)

Note: To kill the web server, use "Ctrl+C" on the console.

Appreciating Interfaces

So now we get to the part where I learned to really appreciate interfaces. As I noted above, piping the Standard Out to a file doesn't work in PowerShell for some reason (although it does work on the macOS terminal). I decided to add another parameter to the application so that it would go straight to a file.

So just like the "web" parameter would start a web server, a "file" parameter would output it to a file.

And this is where I got a bit confused and started fumbling a bit. I was thinking a bit more linearly, so I was thinking about how I could create a file, open the file, and then write whatever came from the "lissajous" function to that file.
I was looking up examples on how to write to a file in Go. But I was on completely the wrong track for this application.
I was getting a bit frustrated because it seemed way too difficult. And that's when I finally looked up exactly what "io.Writer" was (back to the doc: io.Writer):

type Writer interface {
    Write(p []byte) (n int, err error)
}

That's when I realized that I was dealing with an interface, and not some base type. (I was too mired in C# thinking at the time.)

So then, I looked at the "os.File" type (doc here: os.File). Scrolling down a bit, I saw that "File" has a "Write" method:


This method signature matches the io.Writer interface!

Once I had made the connection, the code fell together pretty easily. Here is that part of the "main" method (starting with line 35 in main.go):


The "os.Create" returns a File pointer and an error (docs: os.Create). I assign the file pointer to "gf" (for "gif file") and use an underscore to discard the error object.

Note: I'm skipping error checking here. If you try to compile when you have an unused variable, the compile will fail (a nice safety net). This means that you will often see discards in function and method calls. In this case, we are not checking errors, so we discard the error item that comes back from "os.Create".
Since the file is an "io.Writer", we can pass it directly to the "lissajous" function. This is very convenient (once you start thinking this way).
Now, we can run the application with the "file" argument:


.\lissajous.exe file

And we get a valid animated gif on the file system named "out.gif".


Wrap Up

So, I took the long way around to a convenient solution. But it made me appreciate interfaces in Go that much more.

I really like the minimal approach to interfaces in Go. I don't have to look at a type signature to figure out "is this an io.Writer?" All I need to do is look at the type members themselves. If it has members that satisfy the interface, then I know I can use it wherever that interface is called for.

There are a lot of things to get used to in Go, and there are some conventions to learn. As an example of a convention, a method / function / type that starts with a capital letter is exported (similar to "public" in C#; it can be used from other packages). If it starts with a lower-case letter, it is not exported (similar to "private" in C#).

There are lots of interesting ideas in Go. I'm enjoying my second look at the language.

No comments:

Post a Comment