3

I'm used to Java, and setting first steps in google go. I have a tree of objects with child objects etc... This tree is recursively dumped to an io.Writer. Output might be huge, so I don't want to create a string for each object, and concatenate the result in memory..

For debugging purposes, i want to fmt.Printf parts of this tree. Thus, I want to create a generic String() function on each object in which calls the ToStream function, returning the result as a string. In Java, this is easy: create the method on the base class. How do I do this in GO, without creating a custom String method for each kind of object.

See the code for what I want, specifically the line marked ERROR

package main

import (
"io"
"fmt"
"bytes"
)

//Base is an interface for bulk output
type Base interface {
    ToStream(io.Writer)
}

//Impl1 has interface Base
type Impl1 struct{
    stuff int
}

func (Impl1) ToStream(w io.Writer) {
    fmt.Fprintf(w, "A lot of stuff")
}

//Impl2 has interface Base
type Impl2 struct{
    otherstuff int
}

func (Impl2) ToStream(w io.Writer) {
    fmt.Fprintf(w, "A lot of other stuff")
}

//I want to convert any base to a sting for debug output
//This should happen by the ToStream method

func (v Base) String() string {//ERROR here: Invalid receiver type Base (Base is an interface type)
//func (v Impl1) String() string {//This works, but requires re-implementation for every struct Impl1,Impl2,...
    var buffer bytes.Buffer
    v.ToStream(&buffer)
    return string(buffer.Bytes())
}

func main(){
    aBase:= new(Impl1)
    fmt.Printf("%s\n",aBase)
}
hyperman
  • 1,324
  • 1
  • 11
  • 21
  • The assertion that we can add an implementation to the "Base" class seems wrong: `Base` is an interface. Interfaces don't have methods, at least not until JDK 8, and the original questioner is almost certainly not talking about default interface methods. What the questioner is probably trying to do is use inheritance. Prefer delegation to inheritance approaches. – dyoo Oct 22 '14 at 19:59

3 Answers3

3

Seems like Java thinking blocked you here :-)

While Java has methods only Go does have functions. And of course you cannot have methods on an interface but you can make a plain function taking a Base and doing stuff:

func Base2String(b Base) string {
    var buffer bytes.Buffer
    b.ToStream(&buffer)
    return string(buffer.Bytes())
}

Now if you rename Base to something Go-ish (remember there is no type hierarchy in Go) you have some nice code.

Volker
  • 40,468
  • 7
  • 81
  • 87
1

You can wrap around a Base to add the necessary String() function. Here is one approach:

type StreamerToStringer struct {
    Base
}

func (s StreamerToStringer) String() string {
    var buffer bytes.Buffer
    s.Base.ToStream(&buffer)
    return string(buffer.Bytes())
}

With this, you can augment any Base instance so it has a String() method.

func main() {
    aBase1 := StreamerToStringer{new(Impl1)}
    aBase2 := StreamerToStringer{new(Impl2)}
    fmt.Printf("%s\n", aBase1)
    fmt.Printf("%s\n", aBase2)

    // These wrapped values still support ToStream().
    var buffer bytes.Buffer
    aBase1.ToStream(&buffer)
    fmt.Println(buffer.Bytes())
}
dyoo
  • 11,795
  • 1
  • 34
  • 44
  • Both this and Volkers solution are good paths, but this one allows me to use fmt.Printf without needing a wrapping function call. Therefore, this solution seems preferable for me. Thanks, guys! – hyperman Oct 23 '14 at 07:40
0

It is easier to first call:

fmt.Printf("%+v\n", yourProject)

See if the information printed are enough for a start: the fmt package mentions

when printing structs, the plus flag (%+v) adds field names

If that is not enough, then you would have to use reflection, as I mentioned in "Golang - How to print struct variables in console?".

Or you can have a look at the project davecgh/go-spew (mentioned in "Go-spew: A Journey into Dumping Go Data Structures")

Go-spew implements a deep pretty printer for Go data structures to aid in debugging

spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)

That would print something like:

(main.Foo) {
 unexportedField: (*main.Bar)(0xf84002e210)({
  flag: (main.Flag) flagTwo,
  data: (uintptr) <nil>
 }),
 ExportedField: (map[interface {}]interface {}) {
  (string) "one": (bool) true
 }
}
([]uint8) {
 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
 00000020  31 32                                             |12|
}
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • That's not really what I'm after. The respective ToStream functions already succeed in dumping the objects, in correct format. My question is: how can I write a bridging method between ToStream and fmt.String. And I mean: 1 generic for all the struct's, not 20+ for the 20+ structs which implement that interface. Now, %+v will surely come in handy in other places, so i learnd something anyhow! – hyperman Oct 22 '14 at 12:56
  • @user844382 You still can get idea from the go-spec project: you would need to use reflection. – VonC Oct 22 '14 at 13:06