user3805530
user3805530

Reputation: 328

Is it possible to assert types dynamically in golang?

I have a method Deduplicate that returns deduplicated copy of passed in slice as an interface{}. Is there a way to cast returned by this method interface{} value to the same type as I passed in this method without writing it explicitly? For example, if I change myStruct.RelatedIDs type from []int to []uint it will prevent code from compiling.

https://play.golang.org/p/8OT4xYZuwEn

package main

import (
    "fmt"
    "reflect"
)

type myStruct struct {
    ID         int
    RelatedIDs []int
}

func main() {
    s := &myStruct{
        ID:         42,
        RelatedIDs: []int{1, 1, 2, 3},
    }
    v, _ := Deduplicate(s.RelatedIDs)
    s.RelatedIDs = v.([]int) // << can I assert type dynamically here?
    // s.RelatedIDs = v.(reflect.TypeOf(s.RelatedIDs)) // does not work
    fmt.Printf("%#v\n", s.RelatedIDs)
}

func Deduplicate(slice interface{}) (interface{}, error) {
    if reflect.TypeOf(slice).Kind() != reflect.Slice {
        return nil, fmt.Errorf("slice has wrong type: %T", slice)
    }

    s := reflect.ValueOf(slice)
    res := reflect.MakeSlice(s.Type(), 0, s.Len())

    seen := make(map[interface{}]struct{})
    for i := 0; i < s.Len(); i++ {
        v := s.Index(i)
        if _, ok := seen[v.Interface()]; ok {
            continue
        }
        seen[v.Interface()] = struct{}{}
        res = reflect.Append(res, v)
    }

    return res.Interface(), nil
}

Upvotes: 0

Views: 471

Answers (2)

jub0bs
jub0bs

Reputation: 66244

Updated answer

Since Go 1.21, you can rely on the Compact function from the slices package:

package main

import (
    "fmt"
    "slices"
)

func main() {
    s := []int{1, 1, 2, 3} // assumed sorted
    res := slices.Compact(s)
    fmt.Printf("%#v\n", res)
}

Output:

[]int{1, 2, 3}

(Playground)

And if the compacted slice is much shorter than the original one, you may want to pass it through slices.Clone in order to make the old (large) backing array (and its elements) eligible for GC.

Original answer

For completeness, here is a generic version (Playground), which doesn't require reflection. Only open in February 2022! The usual caveats about NaN apply, though.

package main

import (
    "fmt"
)

func main() {
    s := []int{1, 1, 2, 3}
    res := Deduplicate(s)
    fmt.Printf("%#v\n", res)
}

func Deduplicate[T comparable](s []T) []T {
    seen := make(map[T]struct{})
    res := make([]T, 0, len(s))
    for _, elem := range s {
        if _, exists := seen[elem]; exists {
            continue
        }
        seen[elem] = struct{}{}
        res = append(res, elem)
    }
    return res
}

Output:

[]int{1, 2, 3}

Upvotes: 2

user4466350
user4466350

Reputation:

Try this

package main

import (
    "fmt"
    "reflect"
)

type myStruct struct {
    ID         int
    RelatedIDs []int
}

func main() {
    s := &myStruct{
        ID:         42,
        RelatedIDs: []int{1, 1, 2, 3},
    }
    err := Deduplicate(&s.RelatedIDs)
    fmt.Println(err)
    // s.RelatedIDs = v.([]int) // << can I assert type dynamically here?
    // s.RelatedIDs = v.(reflect.TypeOf(s.RelatedIDs)) // does not work
    fmt.Printf("%#v\n", s.RelatedIDs)
}

func Deduplicate(slice interface{}) error {
    rts := reflect.TypeOf(slice)
    rtse := rts.Elem()
    if rts.Kind() != reflect.Ptr && rtse.Kind() != reflect.Slice {
        return fmt.Errorf("slice has wrong type: %T", slice)
    }

    rvs := reflect.ValueOf(slice)
    rvse := rvs.Elem()

    seen := make(map[interface{}]struct{})
    var e int
    for i := 0; i < rvse.Len(); i++ {
        v := rvse.Index(i)
        if _, ok := seen[v.Interface()]; ok {
            continue
        }
        seen[v.Interface()] = struct{}{}
        rvse.Index(e).Set(v)
        e++
    }

    rvse.SetLen(e)
    rvs.Elem().Set(rvse)

    return nil
}

https://play.golang.org/p/hkEW4u1aGUi

with future generics, it might look like this https://go2goplay.golang.org/p/jobI5wKR8fU

Upvotes: 3

Related Questions