Function receiver with slices

I have this struct defined in some file:

type dbHypervisors struct {
	HID      uint8  `json:"hid" yaml:"hid"`
	Hname    string `json:"hname" yaml:"hname"`
	Haddress string `json:"haddress" yaml:"address"`
}

And I use it this way, later on:

func getHypervisorData(conn *pgx.Conn) []dbHypervisors {
	var hyps []dbHypervisors

	rows, err := conn.Query(context.Background(), "SELECT hID, hName, hAddress from config.hypervisors")
	if err != nil {
		fmt.Println("Error: ", err)
	}
	defer rows.Close()
      <SOME PROCESSING>
      return hyps
}

Now, once the processing is done, I’d wish to serialize the struct in JSON (the code for this works, so the issue is not there. I wanted to implement it through a function type receiver such as:

func (h dbHypervisors) serialize(filename string) {
	//fmt.Printf("%T \n", jsonObject)
}

and from my main function, I’ do something like:

  hypervisors:= getHypervisorData(conn)
  hypervisors.serialize(targetFile)

It complains that serialize() is not resolving. How would I manage to use the function receiver properly ? I guess it expects a dbHypervisor type while I’d wish to use a slice, there.

I want serialize to truncate the file and write everything all at once. I have to go through a for … range loop, it means I’d have to change the logic in serialize() to append each element of the slice, and I want to avoid this.

Any idea ?

You could define a dbHypervisorsSlice type and implement a serialize function on it:

func getHypervisorData(conn *pgx.Conn) dbHypervisorsSlice {
    // ...
}

type dbHypervisorSlice []dbHypervisors

func (hyps dbHypervisorsSlice) serialize(filename string) {
    // write the code that has a for loop to write all of
    // the inner dbHypervisors values.  This way, you
    // don't have to change dbHypervisors' own serialize
    // function.
}

Ok, so since we cannot do function overloading in Go, you suggest that I “overload” the type. This finally gets into that thick skull of mine that Go is all about data types.

I’ll give it a try, thanks again, @skillian !

You cannot overload a function, but you could “disconnect” serialize from dbHypervisors and make it a standalone function:

func serialize(v interface{}, filename string) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    data, err := json.Marshal(v)
    if err != nil {
        return err
    }
    _, err = f.Write(data)
    return err
}

// ...

func main() {
    // ...
    oneHypervisor := dbHypervisors{ /* ... */ }
    if err := serialize(oneHypervisor, "single-hypervisor.json"); err != nil {
        log.Fatal(err)
    }
    twoHypervisors := []dbHypervisors{ /* ... */ }
    if err := serialize(twoHypervisors, "multiple-hypervisors.json"); err != nil {
        log.Fatal(err)
    }

}

Yes, I could do as you suggested, but here’s the thing… I wanted it to be as efficient as possible as I have 3 small tables to export in this module, and each table can be exported as JSON, YAML or SQL; that’s at the end-user’s discretion, so the game here is to write as less code as possible.
Regarding your code sample, using interface{} was my very first choice, but I’ve read confusing articles about using interface{}, when they were not contradictory ! I figured that my understanding of it precluded using it as you just did.

Function overloading as in C++ would have been the obvious choice, but you go with what your language of choice offers you, so here I am.

I’ve just bought myself 2-3 courses on uDemy, I’m fed up trying to chase down information and wasting more time doing that than the actual code writing. Should have done that in the first place, I know…

… I fail to see why you discriminate between a single hypervisor and multi-hypervisors ?

the interface{} parameter wouldn’t care if it’s a slice or not, right ?

(in any case, it is moot here, as all DB code is used only when more than a single hypervisor are present; the DB is what makes my tool “hypervisor-aware”)

Your post after my first suggested provided more information, so I changed my answer :slight_smile:, but both of my answers boil-down to essentially that:

type dbHypervisors != type []dbHypervisors

In your initial question, you posted that your getHypervisorData function returns []dbHypervisors. You then showed that you wanted to be able to say:

hypervisors:= getHypervisorData(conn)
hypervisors.serialize(targetFile)

Which I interpreted as: “I have a type, dbHypervisors, which has a serialize method on it. I want to also be able to have a slice of dbHypervisors and call a single serialize method for them.” My first answer only showed how to accomplish this directly. A slice of dbHypervisors is a distinct type (based on your mention of C++, I would describe []dbHypervisors as similar to std::vector<dbHypervisors>) and just like you cannot do:

std::vector<dbHypervisors> myHypervisors;
// ...
myHypervisors.serialize();

in C++, you cannot do that in Go, either. You instead have to define a new type and add a member function to it; hence: type dbHypervisorsSlice []dbHypervisors in Go, which I think in C++ would be like:

class dbHypervisorsVector : public std::vector<T>
{
    // ...
}

And by having a separate type (dbHypervisorsSlice in Go, dbHypervisorsVector in C++), you can now define a serialize method on it.


In my second suggestion, after you mentioned overloading, it made me think that you could get away with having a single function definition like the one I suggested. In this example, you’re right: There’s no longer a reason to care about dbHypervisors vs. dbHypervisorsSlice, which is why I ditched the dbHypervisorsSlice and used []dbHypervisors instead (in hindsight, I should have made that more explicit).

I understand, @skillian that you’ve replied from incomplete information, and gave the best answer accordingly. For brievety sake I’ve reduced the amount of code I was showing to the bare minimum.
Your C++ class definition is spot-on and would have been the way I’do go, but again, I kept my explanations to the minimum.

That said, the most important thing for me (lesson learned) is what you’ve mentioned:
type dbHypervisors != type []dbHypervisors, which is a bit counter-intuitive IMHO, but I’ll manage :slight_smile:

I’ll go with the “disconnected serialize” solution. That was my very first pick, but I had read (cannot find it anymore in my browser history) that this was not a solution, so I did not even bother trying it. I had thought that the empty interface was a perfect solution (reminds me of the *object object in C++), so I was a bit disappointed reading that it was a no-go.

Again, thanks for taking the time to educate a guy who is just getting his feet wet in Go. I’m building a libvirt client to manage VMs and their resources across multiple hypervisors. It’s a pretty good way to learn a new language, having a problem that needs a solution.

Ok, let’s close this one for real now:

[11:03:12|jfgratton@bergen:source]: jq < hypervisors.json 
[
  {
    "hid": 1,
    "hname": "bergen",
    "haddress": "10.3.1.1"
  },
  {
    "hid": 2,
    "hname": "oslo",
    "haddress": "10.2.1.1"
  }
]

It worked, that’s the expected result.