0

Requirement:

type A struct {//some code}
type B struct {//some code}

func getData(db string) interface{} {
    if db == "dbA" {     // problem with this if condition
        type C A
    } else {
        type C B
    }
    var byteData []byte
    query := //cassandra query object
    iter := query.Iter()
    for iter.Scan(&byteData) {
        myObject := new(C)
        err := proto.Unmarshal(byteData, myObject)
        objects = append(objects, myObject)
    }
    return objects
}

Basically, I don't want to write the if condition inside the loop. But myObject is declared outside the scope of C.

Alternate way: I have tried having the type of myObject as proto.Message, but that gives me error "invalid memory address or nil pointer dereference"

myObject := new(proto.Message)
err := proto.Unmarshal(byteData, myObject)
objects = append(objects, myObject)

Another alternate: I am also not sure if using the same variable will work (and hence i am trying to create a new variable each time inside the loop)

PS: This does not require much Cassandra knowledge

Help appreciated. Thanks !

Edit 1: What I'm trying to accomplish is fetch a few rows from the database. But since I have multiple tables which contain very much similar data, I want to do it in one function in a very much optimised manner. And the values I want to fetch are stored in bytes, which I am using proto to convert into Golang objects.

Edit 2: proto.Unmarshal needs the second argument to be of type proto.Message. Hence I cant use an empty interface. (Both types A and B implement proto.Message)

Edit 3: You can find proto at "github.com/golang/protobuf". I use it to convert objects into bytes and also back into objects.!

Nannan AV
  • 419
  • 7
  • 22
  • 2
    In general, if you want to handle objects of different types with common code, you need to define an interface. The interface encapsulates the common operations you want to perform on the objects. See https://golang.org/doc/effective_go.html#interfaces_and_types for more information. – Andy Schweig Jul 09 '18 at 03:57
  • You cannot do exactly what you want. This seems like an XY Problem. What is the larger goal you're trying to accomplish? There's probably a better way. – Jonathan Hall Jul 09 '18 at 08:03
  • @AndySchweig Please refer to this comment https://stackoverflow.com/questions/51237867/create-type-inside-if-condition#comment89461872_51238483. – Nannan AV Jul 09 '18 at 08:59
  • @Flimzy Added an edit to the question :) – Nannan AV Jul 09 '18 at 09:00

1 Answers1

2

In Go's type system, what you want to achieve can not be done directly.

However, with help of interface{} and first-class function value, we can get it done, and rather gracefully.

To do this, instead of declaring a new type C, we declare a new constructer :

var newC func() interface{}
if true {
    newC = func() interface{} {
        c := new(A)
        return c
    }
} else {
    newC = func() interface{} {
        c := new(B)
        return c
    }
}

And then, in the loop, change myObject := new(C) to myObject := newC(). It is done.

Example: https://play.golang.org/p/dBKBYrLqi_P

Edit:

As you need the param being a proto.Message, which I guess is an interface, you can cast it into proto.Message.

So basically you can re-write you code into:

type A struct {//some code}
type B struct {//some code}

func getData(db string) interface{} {
var newC func() interface{}
if true {
    newC = func() proto.Message {
        c := new(A)
        return c
    }
} else {
    newC = func() proto.Message {
        c := new(B)
        return c
    }
}
var byteData []byte
    query := //cassandra query object
    iter := query.Iter()
    for iter.Scan(&byteData) {
        myObject := newC()
        err := proto.Unmarshal(byteData, myObject)
        objects = append(objects, myObject)
    }
    return objects
}
leaf bebop
  • 7,643
  • 2
  • 17
  • 27
  • 1
    This looks a lot more complex than necessary. Why not just [this](https://gist.github.com/flimzy/d6594065cd57704be286fef384733524)? – Jonathan Hall Jul 09 '18 at 07:48
  • @Flimzy This will check the condition multiple times, and lacks extendability. If the condition in `if` statement is trivial, it is not a bad idea. But in most cases, where the condition might be complex or separate from usage code, it is inefficient or unviable. – leaf bebop Jul 09 '18 at 07:53
  • I tried using myObject := new(proto.Message). (Since proto.Unmarshal needs the second argument to be of type proto.Message and not just an empty interface). I have added this in the question as "Alternate way". – Nannan AV Jul 09 '18 at 08:55
  • @NannanAV I edited the answer. Can you please tell us what `proto` is? which package are you using? – leaf bebop Jul 09 '18 at 09:13
  • @leafbebop Added details about proto in the question. Also, As per your answer, the newC func will be executed in each iteration in the for loop, which is what I want to avoid (since it returns the same thing every time) – Nannan AV Jul 09 '18 at 09:24
  • @NannanAV You would have to execuate some code every loop, either this function, or `new(C)`. It is unavoidable since using the same variable will not make things better. Edited my answer according to the updated info. – leaf bebop Jul 09 '18 at 10:02
  • Also, a single function call is very cheap in Go. – leaf bebop Jul 09 '18 at 10:03