83

Hi I'm trying to retrieve the function/method of one struct but I'm using an interface as parameter and using this interface I'm trying to access the function of the struct. To demonstrate what I want below is my code

// Here I'm trying to use "GetValue" a function of RedisConnection but since "c" is an interface it doesn't know that I'm trying to access the RedisConnection function. How Do I fix this?
func GetRedisValue(c Connection, key string) (string, error) {
    value, err := c.GetValue(key)

    return value, err
}

// Connection ...
type Connection interface {
    GetClient() (*redis.Client, error)
}

// RedisConnection ...
type RedisConnection struct {}

// NewRedisConnection ...
func NewRedisConnection() Connection {
    return RedisConnection{}
}

// GetClient ...
func (r RedisConnection) GetClient() (*redis.Client, error) {
    redisHost := "localhost"
    redisPort := "6379"

    if os.Getenv("REDIS_HOST") != "" {
        redisHost = os.Getenv("REDIS_HOST")
    }

    if os.Getenv("REDIS_PORT") != "" {
        redisPort = os.Getenv("REDIS_PORT")
    }

    client := redis.NewClient(&redis.Options{
        Addr:     redisHost + ":" + redisPort,
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    return client, nil
}

// GetValue ...
func (r RedisConnection) GetValue(key string) (string, error) {
    client, e := r.GetClient()
    result, err := client.Ping().Result()
    return result, nil
}
MadzQuestioning
  • 3,341
  • 8
  • 45
  • 76

2 Answers2

224

To answer the question directly, i.e., to cast an interface into a concrete type, you do:

v = i.(T)

where i is the interface and T is the concrete type. It will panic if the underlying type is not T. To have a safe cast, you use:

v, ok = i.(T)

and if the underlying type is not T, ok is set to false, otherwise true. Note that T can also be an interface type and if it is, the code cast i into a new interface instead of a concrete type.

And please be noted, casting an interface is likely a symbol of bad design. As in your code, you should ask yourself, does your custom interface Connection solely requires GetClient or does it always requires a GetValue? Does your GetRedisValue function requires a Connection or does it always wants a concrete struct?

Change your code accordingly.

lloiacono
  • 4,714
  • 2
  • 30
  • 46
leaf bebop
  • 7,643
  • 2
  • 17
  • 27
  • 1
    I was so happy to find that this type of casting was possible in golang that I was about to start using it when I read you last paragraph... and then :( go back to the design diagrams and avoid using this type of casting. You are absolutely right on your note, thanks! – Edenshaw Mar 29 '22 at 02:56
13

Your Connection interface:

type Connection interface {
    GetClient() (*redis.Client, error)
}

only says that there is a GetClient method, it says nothing about supporting GetValue.

If you want to call GetValue on a Connection like this:

func GetRedisValue(c Connection, key string) (string, error) {
    value, err := c.GetValue(key)
    return value, err
}

then you should include GetValue in the interface:

type Connection interface {
    GetClient() (*redis.Client, error)
    GetValue(string) (string, error) // <-------------------
}

Now you're saying that all Connections will support the GetValue method that you want to use.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Yeah I figured that will be the case. But it doesn't make sense that the Connection interface will have a GetValue method. So I was thinking of having a separate interface with a GetValue function in it. How will I go about that? – MadzQuestioning Jun 20 '18 at 05:27
  • 1
    That depends on how far down the rabbit hole you want to go. Do you really need your `Connection` interface? Maybe you only need some sort of `type Valuer interface { GetValue(string) (string, error) }` then just wrap your redis connection in a struct that implements that interface. – mu is too short Jun 20 '18 at 16:25