testing495
testing495

Reputation: 272

Correct way to pass interfaces to methods

Preference: I'm new to Golang and keen to improve.

So I'm trying to learn from Dave Cheney's talk here: https://youtu.be/NwEuRO_w8HE?t=812 where we pass interfaces to methods to make code clearer and more generic.

I've implemented an example below, where I have a struct that can be outputted either to the std.out or to a file. However, I feel like it's a bit redundant just making empty structs (called "print" and "save" in ,my example), just so I can attach methods. How can I improve this?

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

type file struct {
    data string
}

func (f *file) output(w io.Writer) error {
    len, err := w.Write([]byte(f.data))
    if err != nil {
        return err
    } else {
        log.Println("Bytes Out: ", len)
        return nil
    }
}

type print struct{}

func (print *print) Write(p []byte) (n int, err error) {
    return fmt.Printf("%s\n", p)
}

type save struct{}

func (s *save) Write(p []byte) (n int, err error) {
    err = ioutil.WriteFile("./dat1", []byte(p), 0644)
    if err != nil {
        return -1, err
    }
    return len(p), nil
}
func main() {
    f := file{"test"}
    s := save{}
    p := print{}

    f.output(&s)
    f.output(&p)
}

Upvotes: 1

Views: 567

Answers (2)

Adrian
Adrian

Reputation: 46442

You don't need print or save types here. You have a method, output, that takes an io.Writer; the whole point of that is that you can pass it any writer. Both stdout and files are writers, so everything else here is unnecessary. You could just:

func main() {
    f := file{"test"}

    fp,err := os.Create("./dat1")
    if err != nil {
        panic(err)
    }
    f.output(fp)  // This writes to a file
    f.output(os.Stdout)  // This writes to stdout
}

Upvotes: 1

icza
icza

Reputation: 417622

Your intentions aren't clear, but to answer your original question: how to pass functions as interface values?

You could create a single adapter-like type that implements io.Writer, and when its Write() method is called, it forwards to a function of your choice. A typical example of this is the http.HandlerFunc: it's a function type that implements http.Handler, so a function can be passed when an http.Handler implementation is required.

For an io.Writer adapter, it could look like this:

type WriteFunc func(p []byte) (n int, err error)

func (w WriteFunc) Write(p []byte) (n int, err error) {
    return w(p)
}

And if you have functions like these:

func PrintWrite(p []byte) (n int, err error) {
    return fmt.Printf("%s\n", p)
}

func SaveWrite(p []byte) (n int, err error) {
    err = ioutil.WriteFile("./dat1", []byte(p), 0644)
    if err != nil {
        return -1, err
    }
    return len(p), nil
}

You can use them as io.Writer like this:

f.output(WriteFunc(PrintWrite))
f.output(WriteFunc(SaveWrite))

Try it on the Go Playground.

Upvotes: 2

Related Questions