Dynamic function call from generics depending on type

Issue

Is it possible to use generics to call a function depending on the type of parameters?
I will try to explain my task in as much detail as possible.

For example, I have a structure with two methods:

package main

import "reflect"

type Cut interface {
    int | int8 | int16 | int32 | int64 | float32 | float64 | string
}

type AudioObj struct {
    fileName string
}

func (A AudioObj) CutIfFirstIsString(from string, to float64) {
    // Cut audio file: ("00:00:03", 81.0)
}

func (A AudioObj) CutIfSecondIsString(from float64, to string) {
    // Cut audio file: (0.3, "01:21:00")
}

And this is my generic:

func CutAudio[S Cut, E Cut](MusicFile AudioObj, start S, end E) {
    // The first parameter is a string.
    if reflect.ValueOf(start).Kind() == reflect.String {
        MusicFile.CutIfFirstIsString(start, end)
    }

    // The second parameter is a string.
    if reflect.ValueOf(end).Kind() == reflect.String {
        MusicFile.CutIfSecondIsString(start, end)
    }
}

Here I try to cut the audio file depending on function parameters type:

func main() {
    audio := AudioObj{fileName: "music.mp3"}

    CutAudio(audio, "00:00:03", 81.0)
    CutAudio(audio, 0.3, "01:21:00")
}

Output:

./prog.go:24:32: cannot use start (variable of type S constrained by Cut) as type string in argument to MusicFile.CutIfFirstIsString
./prog.go:24:39: cannot use end (variable of type E constrained by Cut) as type float64 in argument to MusicFile.CutIfFirstIsString
./prog.go:29:33: cannot use start (variable of type S constrained by Cut) as type float64 in argument to MusicFile.CutIfSecondIsString
./prog.go:29:40: cannot use end (variable of type E constrained by Cut) as type string in argument to MusicFile.CutIfSecondIsString

PlayGround: https://go.dev/play/p/1jx1-vHXDdn

It is posibile to convert generic type into the type that I sended to it?

Solution

You can do this with a good old type switch, but it is a little bit verbose:

package main

import (
    "fmt"
    "strconv"
)

type Cut interface {
    int | int8 | int16 | int32 | int64 | float32 | float64 | string
}

type AudioObj struct {
    fileName string
}

func (A AudioObj) CutIfFirstIsString(from string, to float64) {
    // Cut audio file: ("00:00:03", 81.0)
    fmt.Println(`Cut audio file: ("00:00:03", 81.0)`)
}

func (A AudioObj) CutIfSecondIsString(from float64, to string) {
    // Cut audio file: (0.3, "01:21:00")
    fmt.Println(`Cut audio file: (0.3, "01:21:00")`)
}

func CutAudio[S Cut, E Cut](MusicFile AudioObj, start S, end E) {
    // The first parameter is a string.
    if _, ok := any(start).(string); ok {
        MusicFile.CutIfFirstIsString(cutToString(start), cutToFloat(end))
    }

    // The second parameter is a string.
    if _, ok := any(end).(string); ok {
        MusicFile.CutIfSecondIsString(cutToFloat(start), cutToString(end))
    }
}

func cutToString[S Cut](cut S) string {
    var str string
    switch start := any(cut).(type) {
    case int:
        str = strconv.FormatInt(int64(start), 10)
    case int8:
        str = strconv.FormatInt(int64(start), 10)
    case int16:
        str = strconv.FormatInt(int64(start), 10)
    case int32:
        str = strconv.FormatInt(int64(start), 10)
    case int64:
        str = strconv.FormatInt(start, 10)
    case float32:
        str = strconv.FormatFloat(float64(start), 'G', -1, 32)
    case float64:
        str = strconv.FormatFloat(start, 'G', -1, 64)
    case string:
        str = start
    }

    return str
}

func cutToFloat[S Cut](cut S) float64 {
    var flt float64
    switch end := any(cut).(type) {
    case int:
        flt = float64(end)
    case int8:
        flt = float64(end)
    case int16:
        flt = float64(end)
    case int32:
        flt = float64(end)
    case int64:
        flt = float64(end)
    case float32:
        flt = float64(end)
    case float64:
        flt = end
    case string:
        flt, _ = strconv.ParseFloat(end, 64)
    }
    return flt
}

func main() {
    audio := AudioObj{fileName: "music.mp3"}

    CutAudio(audio, "00:00:03", 81.0)
    CutAudio(audio, 0.3, "01:21:00")
}

Playground link

Answered By – Dylan Reimerink

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.