Skip to content

Instantly share code, notes, and snippets.

  • Save christophberger/2ce8d641cddb5bbedfc91042cf3697de to your computer and use it in GitHub Desktop.
Save christophberger/2ce8d641cddb5bbedfc91042cf3697de to your computer and use it in GitHub Desktop.
The Package reflect in the Go standard library implements run-time reflection, allowing a program to manipulate objects with arbitrary types.
We can build with the reflection package a kind of generic functionality. Because this package give us the possibility to determine the meta information of a function and it's input and output arguments while runtime.
Be careful when you start to use the reflection package it's hard to debug run time errors. For more information about reflection, I can recommend the following article about reflection.
See "The Laws of Reflection" for an introduction to reflection in Go: https://golang.org/doc/articles/laws_of_reflection.html
If you want see reflection in action below is a example:
package concurrent
import (
"sync"
"reflect"
)
// Run executes the provided functions in concurrent and collects any errors they return.
func ReflectRun(fns ...[]interface{}) []error {
wg := sync.WaitGroup{}
errCh := make(chan error, len(fns))
wg.Add(len(fns))
for _, fn := range fns {
if len(fn) <= 1 {
panic("Must be a pair of func and input values")
}
fnType := reflect.TypeOf(fn[0])
// panic if conditions not met (because it's a programming error to have that happen)
switch {
case fnType.Kind() != reflect.Func:
panic("value must be a function")
case fnType.NumOut() != 1:
panic("func must have exactly one output argument")
}
// the first output parameter must be a error
outType := fnType.Out(0)
if ok := outType.Implements(reflect.TypeOf((*error)(nil)).Elem()); !ok {
panic("func output argument must be a error")
}
inputValues := []reflect.Value{}
for i, valRaw := range fn[1:] {
if fnType.In(i).Kind() != reflect.TypeOf(reflect.ValueOf(valRaw).Interface()).Kind(){
panic("func input value is bad")
}
inputValues = append(inputValues, reflect.ValueOf(valRaw))
}
go func(fn interface{}, inputValues []reflect.Value) {
outputValues := reflect.ValueOf(fn).Call(inputValues)
if err, ok := outputValues[0].Interface().(error); ok && err != nil {
errCh <- err
}
wg.Done()
}(fn[0], inputValues)
}
wg.Wait()
close(errCh)
var errs []error
for err := range errCh {
errs = append(errs, err)
}
return errs
}
package concurrent
import (
"fmt"
"sync/atomic"
"testing"
)
func TestReflectRun(t *testing.T) {
i := int32(0)
fn1 := []interface{}{
func(ii int32) error {
i = atomic.AddInt32(&ii, 3)
return nil
},
int32(3),
}
fn2 := []interface{}{
func(ii int32) error {
i = atomic.AddInt32(&ii, 3)
return nil
},
int32(3),
}
errs := ReflectRun(
fn2,
fn1,
)
if len(errs) != 0 || i != 6 {
t.Errorf("unexpected run")
}
testErr := fmt.Errorf("an error")
y := int32(0)
fn3 := []interface{}{
func(yy int32) error {
return testErr
},
int32(3),
}
fn4 := []interface{}{
func(yy int32) error {
y = atomic.AddInt32(&yy, 3)
return nil
},
int32(3),
}
errs = ReflectRun(
fn3,
fn4,
)
if len(errs) != 1 && errs[0] != testErr && y != 3 {
t.Error("unexpected run")
}
x := 0
fn5 := []interface{}{
func(xx int) error {
xxx := int32(xx)
x = int(atomic.AddInt32(&xxx, 3))
return nil
},
3,
}
fn6 := []interface{}{
func(xx int) error {
xxx := int32(xx)
x = int(atomic.AddInt32(&xxx, 3))
return nil
},
3,
}
errs = ReflectRun(
fn5,
fn6,
)
if len(errs) != 0 || x != 6 {
t.Errorf("unexpected run ")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment