2

Is there anything equivalent in Go to the dynamic Class instantiation capabilities provided by languages like Java (note: requisite exception handling logic has been omitted here for the sake of brevity):

Class cls = Class.forName("org.company.domain.User");
Constructor<User> userConstructor = cls.getConstructor();
User user1 = userConstructor.newInstance();

The short Java snippet above essentially grabs reference to the Class via the supplied fully qualified classpath string, the class reference is then used to obtain reference to a zero-argument constructor (where one exists) and finally the constructor is used to obtain reference to an instance of the class.

I've yet to find an example of a similar mechanism in Go that may achieve similar results. More specifically, it would seem that the reflect package in go requires that the caller already have reference to the type of struct they wish to instantiate. The standard idiom in this regard seems to be as follows:

reflect.New(reflect.TypeOf(domain.User))

Note: The argument provided to the reflect.TypeOf function MUST be a Type not a string. Can a struct be instantiated in Go, via the reflect package, using nothing more than its fully qualified name?

Giles Thompson
  • 1,097
  • 1
  • 9
  • 24
  • 4
    No, and idiomatic Go code wouldn't even try to do this. What problem are you trying to solve? – Adrian Jun 08 '21 at 16:17
  • 1
    Go programs typically handle this scenario by creating a registry that maps a string to a function that creates a value. See [sql.Register](https://pkg.go.dev/database/sql#Register) for an example. – Charlie Tumahai Jun 08 '21 at 16:20
  • Ok, I suspected that may well be the case. Thanks for the definitive answer on this Adrian. – Giles Thompson Jun 08 '21 at 16:22
  • Thanks Cerise, I had come across that approach, trouble is it requires one to know all the types to be instantiated at compile time doesn't it? The inherent power of the Java solution I presented is it also applies to interfaces. Thus ANY class can be dynamically loaded in from the classpath or indeed even over the network and instantiated as long as it implements the said interface. – Giles Thompson Jun 08 '21 at 16:26
  • 4
    If your code does not explicitly refers to a type, there's no guarantee it ends up in your executable binary. End of discussion. If it doesn't, obviously there's no way to create a value of that type. – icza Jun 08 '21 at 16:29
  • 2
    @GilesThompson If packages register contained types in an `init` function, then applications must ensure that the relevant packages are imported. Per the previous comment, the application must do that anyway. – Charlie Tumahai Jun 08 '21 at 16:34

1 Answers1

5

Kubernetes handles this exact process in the runtime.Scheme structure. The idea is that you register types with names or some other identifier, then you can ask for new instances of those types at will based on the identifier. Generally speaking this identifier is derived during a serialization process for example, rather then hard-coded into the source.

The catch is as you said, you need to create a new instance initially. While this pattern is un-common, I have come across two cases in my professional career where this was the logical solution. Here is an example of a very stripped down version of what the K8s runtime.Scheme does to accomplish this and it may work for what you're trying to do and here it is in action:

package main

import (
    "fmt"
    "reflect"
)

type Scheme struct {
    types map[string]reflect.Type
}

func (s *Scheme) RegisterType(name string, t interface{}) {
    a := reflect.TypeOf(t)
    s.types[name] = a
}

func (s *Scheme) New(name string) (interface{}, error) {
    t, ok := s.types[name]
    if !ok {
        return nil, fmt.Errorf("unrecognized type name: %s", name)
    }
    return reflect.New(t).Interface(), nil
}

func NewScheme() *Scheme {
    return &Scheme{types: map[string]reflect.Type{}}
}

type MyType struct {
    Foo string
}

func main() {
    scheme := NewScheme()
    scheme.RegisterType("my.type", MyType{})
    myType, _ := scheme.New("my.type")
    myType.(*MyType).Foo = "bar"
    fmt.Println("%+v", myType)
}
Clark McCauley
  • 1,342
  • 5
  • 16
  • Thanks fo your answer Clark. I had come across this approach, as mentioned above. Its most certainly not as elegant as the Java solution but it may well be the only way to come anywhere near to what I'm trying to accomplish. – Giles Thompson Jun 08 '21 at 16:37
  • Just wondering: how is having your type represented as a string more "elegant"? Magic strings are almost always a terrible idea in my experience, and in your Java code, `User` is a type not a string. Thus, your Java code has types in it as well and is not 100% string-based. – Dean Jun 08 '21 at 16:44
  • @Dean This solution is not more elegant in my opinion, however there are cases where you need to dynamically unmarshal data into a custom type for example. Say that you know all your types will have a `metadata` property that indicates the type of the data. Then based on that `metadata` property, you retrieve the custom type from the `Scheme` and unmarshal the data into the custom type. I don't think this is elegant but I do think it is flexible. – Clark McCauley Jun 08 '21 at 16:46
  • 1
    @Dean it's more common that they're not "magic strings" in your code, but from external sources (for example in serialization). – Hymns For Disco Jun 08 '21 at 16:48
  • @Dean Actually, in Java you can completely omit the type. It is actually possible to replace the User in the snippet I provided above with a base Object. Similarly the generic parameter on the constructor is ALSO not entirely necessary and thus you really DO NOT need to know the specific type of the Class to be instantiated ahead of time. – Giles Thompson Jun 08 '21 at 16:51
  • @GilesThompson yes, but what will you do with your dynamically instantiated type if you have no clue what the type is? Obviously you will need to do some sort of type checking at **some** point because you're going to want to do something different with a `User` object than, say, an `Invoice` object, right? – Dean Jun 08 '21 at 16:56
  • @ClarkMcCauley I hear what you're saying. I have, on occasion, had a handler that was very generic. For example, receiving Stripe webhooks that can be of many different types. However, I don't want to handle payment succeeded the same way I want to handle a subscription cancelled event, right? You're going to want to, at some point, care about the type of your request and thus trying to be incredibly generic up front buys you nothing. At some point you will have to do something based on type. And if you don't, you should have a more generically typed object in the first place. – Dean Jun 08 '21 at 17:06
  • 1
    @Dean So essentially what you can do is have a base INTERFACE type, and then dynamically instantiate ANY class that implements that interface at runtime. So a simple, example might be the concept of a Filter Interface. The core application may want to afford the user the ability to apply an arbitrary number of filters in any order dynamically. By defining a Filter Interface with perhaps an "apply" method then the application can use the SAME method to ONLY load the Filter implementations it requires at runtime and apply them. It does not need to care about specific implementations. – Giles Thompson Jun 08 '21 at 17:07