I've tried to create a function in Go that used to retry any fail query functions (usually because serialization issue).
func retryer(functionA func(interface{}) (interface{}, []error), maxRetry int, waitBetween time.Duration) interface{} {
//when no error in functionA, retryer returns whatever functionA returns
//when maxRetry is reached, returns nil
}
The functions I want to retry are looked like this
func GetTopStudent(classId string) ([]Student, []error) {
//queries top 10 students for class with classId
}
func GetAverageStudentScores(classId string, from time.Time, until time.Time) ([]Pair, []error) {
//queries all average score using aggregate, grouping by studentId
//Pair behaves like C++ pair<string,string>
}
But, the results is a compile error
cannot use GetTopStudent (type func(string) ([]Student, []error)) as type func(interface{}) (interface {}, []error) in argument to retryer
I've tried to modify it a little and I got another compile error
cannot use GetTopStudent (type func(string) ([]Student, []error)) as type func(string) (interface {}, []error) in argument to retryer
Can anyone help me creating a general function to wrap a function to retry on error?
A better way to solve your problem would be to use closures.
For example, change the type of retryer:
func retryer(f func() error, maxRetry int, waitBetween time.Duration) error {
// retry and wait logic
err := f()
// error handling, retry, and wait logic
return err
}
Now call functions to be retried as:
// ...
classId := "some value"
// ...
var st []Student
var errors []error
err := retryer(func() error {
st, errors = GetTopStudent(classId)
// handle errors
return nil
}, numTries, waitTime)
// use st here
Related
I'm a beginner with Go, and I'm now writing a function which can call an API. The function receives a part of the url (/user, /account, etc) and the struct to convert the returned json to (the structs User or Account for example) as arguments.
So I now have this:
func (self *RestClient) request(action string, return_type interface{}) interface{} {
res, _ := goreq.Request{Uri:self.url + action}.Do()
var item return_type
res.Body.FromJsonTo(&item)
return item
}
And I try to call this function using (with Index being the struct for the return type):
self.request("/api/v1/public/index", Index)
But this doesn't work. I get the following errors:
return_type is not a type
type Index is not an expression
I guess I understand why this is. I think I have to somehow find out the type of return_type and then convert return_type to that type, before using it as the type for item. I have no idea how though.
About the second error I have no idea. I don't even understand what is meant by it.
Could anybody help me out? How can I make this work? Or should this be done in a completely different way all together? All tips are welcome!
A few hints based on this code:
Don't use self - use a meaningful identifier
Don't use interface{} to avoid dealing with the type system
Don't use reflection
Don't ignore errors returned (as from FromJsonTo or goreq.Request)
Don't use a library like goreq unless you are sure you need it (you don't) - you are pulling in lots of code you don't need and it is teaching you bad habits like attempting to use empty interface and reflection to solve simple problems.
Have a look at the definition of FromJsonTo - if you look through this library you'll see it isn't saving you much effort and is adding lots of complexity. Here is how you could do it without the library:
func (c *RestClient) Request(action string, resource interface{}) error {
res, err := http.Get(c.url + action)
if err != nil {
return err
}
defer res.Body.Close()
return json.NewDecoder(res.Body).Decode(resource)
}
Or use an interface and move the decoding to the resource (which could embed a default decoder):
type Decoder interface {
Decode(r io.Reader) error
}
// RequestDecode fetches a request and feeds it to the decoder
func (c *RestClient) RequestDecode(action string, resource Decoder) error {
res, err := http.Get(c.url + action)
if err != nil {
return err
}
defer res.Body.Close()
return resource.Decode(res.Body)
}
I'll first say that you should always check for errors from any function that possibly returns one.
The error you are seeing is because you are trying to declare a variable item as type return_type and that is the name of a function argument.
The other error is from Index being a type declaration and not a concrete value of the Index type.
I agree with Volker's comment but to put it in code, you could use something like this:
func (self *RestClient) request(action string, return_type interface{}) {
res, err := goreq.Request{Uri:self.url + action}.Do()
if err != nil {
// Do something with error here.
}
res.Body.FromJsonTo(return_type)
}
var index Index
rest_client.request("/some/path", &index)
This allows flexibility but could lead to strange cases if you forget to pass a pointer to the value return_type.
So I would like to fill any struct via the StructScan method and so read any data I get from the db into the regarding struct I feed the test function.
This script doesn't give any compile error (if you implement the other stuff like a db connection and so on) but still the StructScan method returns an error and tells me that it expects a slice of structs.
How do I create a slice of structs that I don't know the type of?
Thanks for any advice.
package main
import (
"database/sql"
"github.com/jmoiron/sqlx"
)
var db *sql.DB
type A struct {
Name string `db:"name"`
}
type B struct {
Name string `db:"name"
}
func main() {
testA := []A{}
testB := []B{}
test(testA, "StructA")
test(testB, "StructB")
}
func test(dataStruct interface{}, name string) {
rows, err := db.Query("SELECT * FROM table WHERE name =", name)
if err != nil {
panic(err)
}
for rows.Next() {
err := sqlx.StructScan(rows, &dataStruct)
if err != nil {
panic(err)
}
}
}
Super late to the party, but ran into this question while researching another issue. For others that stumble upon it, the problem is that you're passing a pointer to dataStruct into StructScan(). dataStruct is an interface, and pointers to interfaces are almost always an error in Go (in fact, they removed the automatic dereferencing of interface pointers a few versions back). You're also passing in your destination by value.
So, you are passing a pointer to an interface that holds a copy of your destination slice, when what you want instead is to pass the interface directly, and that interface to hold a pointer to your destination slice.
Instead of:
test(testA, "StructA")
test(testB, "StructB")
// ...
err := sqlx.StructScan(rows, &dataStruct)
Use:
test(&testA, "StructA")
test(&testB, "StructB")
// ...
err := sqlx.StructScan(rows, dataStruct)
If you have no idea what the destination struct type is, use sqlx.MapScan or sqlx.SliceScan. They don't map to a struct, but both return all the columns from the query result.
See http://jmoiron.github.io/sqlx/#altScanning
I am trying to use inline if condition as follows:
topDisplay.text!.rangeOfString(".") != nil ? call function A : call function B
The idea here is if there is "." in the topDisplay.text! then call function A, otherwise, call function B. The method, rangeOfString, returns nil if no "." is found. So I am wondering is it possible to check nil within inline condition expression and making function call at the same time.
Your code is correct, assuming you put the calls to actual functions in there, i.e.:
func functionA() -> String { return "A" }
func functionB() -> String { return "B" }
topDisplay.text?.rangeOfString(".") != nil ? functionA() : functionB()
If you’re getting an error message, the most likely reason is functionA and functionB return different types:
func functionA() -> String { return "A" }
func functionB() -> Int { return 1 }
// error: could not find an overload for '!=' that accepts the supplied arguments
topDisplay.text?.rangeOfString(".") != nil ? functionA() : functionB()
In classic Swift error message style, this gives you an error about the valid != comparison not about the problem with A and B returning incompatible types.
However, given you aren’t showing in your code the assignment of the result, I suspect you are actually wanting to run these functions for their side-effects not for the value they return. If this is the case do not do this. The ?: operator is there for evaluating two possibilities as an expression. If you want side effects, use an if:
if topDisplay.text?.rangeOfString(".") != nil {
sideEffectfulFunctionA()
}
else {
sideEffectfulFunctionB()
}
With the following code, if no file argument is given, a panic is thrown for line 9 panic: runtime error: index out of range as expected.
How can I 'catch' this panic and handle it when directly when passing something to it (os.Args[1]) that causes the panic? Much like try/catch in PHP or try/except in Python.
I've had a search here on StackOverflow but I've not found anything that answers this as such.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open(os.Args[1])
if err != nil {
fmt.Println("Could not open file")
}
fmt.Printf("%s", file)
}
A panicking program can recover with the builtin recover() function:
The recover function allows a program to manage behavior of a panicking goroutine. Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing. When the running of deferred functions reaches D, the return value of D's call to recover will be the value passed to the call of panic. If D returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G and the call to panic is discarded, and normal execution resumes. Any functions deferred by G before D are then run and G's execution terminates by returning to its caller.
The return value of recover is nil if any of the following conditions holds:
panic's argument was nil;
the goroutine is not panicking;
recover was not called directly by a deferred function.
Here is an example of how to use this:
// access buf[i] and return an error if that fails.
func PanicExample(buf []int, i int) (x int, err error) {
defer func() {
// recover from panic if one occured. Set err to nil otherwise.
if (recover() != nil) {
err = errors.New("array index out of bounds")
}
}()
x = buf[i]
}
Notice that more often than not, panicking is not the right solution. The Go paradigm is to check for errors explicitly. A program should only panic if the circumstances under which it panics do not happen during ordinary program executing. For instance, not being able to open a file is something that can happen and should not cause a panic while running out of memory is worth a panic. Nevertheless, this mechanism exists to be able to catch even these cases and perhaps shut down gracefully.
Go is not python, you should properly check for args before you use it:
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s [filename]\n", os.Args[0])
os.Exit(1)
}
file, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", file)
}
Some Golang official packages use panic/defer+recover as throw/catch, but only when they need to unwind a large call stack. In Golang's json package using panic/defer+recover as throw/catch is the most elegant solution.
from http://blog.golang.org/defer-panic-and-recover
For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in decode.go).
Search for d.error(
at http://golang.org/src/encoding/json/decode.go
In your example the "idiomatic" solution is to check the parameters before using them, as other solutions have pointed.
But, if you want/need to catch anything you can do:
package main
import (
"fmt"
"os"
)
func main() {
defer func() { //catch or finally
if err := recover(); err != nil { //catch
fmt.Fprintf(os.Stderr, "Exception: %v\n", err)
os.Exit(1)
}
}()
file, err := os.Open(os.Args[1])
if err != nil {
fmt.Println("Could not open file")
}
fmt.Printf("%s", file)
}
First: You wouldn't want to do this. Try-catch-style error handling is no error handling. In Go you would check len(os.Args) first and access element 1 only if present.
For the rare cases you need to catch panics (and your case is not one of them!) use defer in combination with recover. See http://golang.org/doc/effective_go.html#recover
We can manage panic without halting process using recover. By calling recover in any function using defer it will return the execution to calling function. Recover returns two values one is boolean and other one is interface to recover. Using type assertion we can get underlying error value
You can also print underlying error using recover.
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
I had to catch panics in a test case. I got redirected here.
func.go
var errUnexpectedClose = errors.New("Unexpected Close")
func closeTransaction(a bool) {
if a == true {
panic(errUnexpectedClose)
}
}
func_test.go
func TestExpectedPanic() {
got := panicValue(func() { closeTransaction(true) })
a, ok := got.(error)
if a != errUnexpectedClose || !ok {
t.Error("Expected ", errUnexpectedClose.Error())
}
}
func panicValue(fn func()) (recovered interface{}) {
defer func() {
recovered = recover()
}()
fn()
return
}
Used from https://github.com/golang/go/commit/e4f1d9cf2e948eb0f0bb91d7c253ab61dfff3a59 (ref from VonC)
Note that the recover treatment of a panic Execution error (such as attempting to index an array out of bounds trigger) might change with go 1.7 after issue 14965
See CL 21214 and its test:
runtime: make execution error panic values implement the Error interface
Make execution panics implement Error as mandated by Run-time panics (specs), instead of panics with strings.
When you recover a panic error, you would be able to do:
if _, ok := recovered.(runtime.Error); !ok {
This is still being evaluated, and as Dave Cheney. mentions:
I don't know what people are currently doing but from my POV this has been broken for a long time and nobody has complained so they are either explicitly relying on the broken behaviour, or nobody cares. Either way I think it's a good idea to avoid making this change.
I have to write unit tests for several functions with similar signature and return values (an object and an error), which must pass similar test conditions.
I would like to avoid writing:
func TestFunc1(t *testing.T) {
// tests on return values
}
func TestFunc2(t *testing.T) {
// tests identical for Func1
}
func TestFunc3(t *testing.T) {
// tests identical for Func1
}
...
(See this go playground example for a more complete context)
(yes, go playground doesn't support yet go test, only go run, and issue 6511 is there to request that feature)
How would you use reflection (reflect package) in order to write only one test which would:
call each function in turn?
test their return value?
I have seen:
"How to properly use .Call in reflect package, Golang?", using Value.Call
"Selecting a function from a list of functions in Golang"
But I miss a complete example for calling functions and using the returned values in a test.
Once I understood that everything must use or return the type Value, here is what I came up with.
The trick is to use:
ValueOf in order to get a value of the receiver
Value.MethodByName to find a function of that receiver value
Value.IsNil to test for nil returned value.
Main extract of the test code:
var funcNames = []string{"Func1", "Func2", "Func3"}
func TestFunc(t *testing.T) {
stype := reflect.ValueOf(s)
for _, fname := range funcNames {
fmt.Println(fname)
sfunc := stype.MethodByName(fname)
// no parameter => empty slice of Value
ret := sfunc.Call([]reflect.Value{})
val := ret[0].Int()
// That would panic for a nil returned err
// err := ret[1].Interface().(error)
err := ret[1]
if val < 1 {
t.Error(fname + " should return positive value")
}
if err.IsNil() == false {
t.Error(fname + " shouldn't err")
}
}
}
See a runnable example in go playground.
Note that if you are calling that test function with a non-existent function name, that will panic.
See that example here.
runtime.panic(0x126660, 0x10533140)
/tmp/sandbox/go/src/pkg/runtime/panic.c:266 +0xe0
testing.func·005()
/tmp/sandbox/go/src/pkg/testing/testing.go:383 +0x180
----- stack segment boundary -----
runtime.panic(0x126660, 0x10533140)
/tmp/sandbox/go/src/pkg/runtime/panic.c:248 +0x160
reflect.flag.mustBe(0x0, 0x13)
/tmp/sandbox/go/src/pkg/reflect/value.go:249 +0xc0
reflect.Value.Call(0x0, 0x0, 0x0, 0xfeef9f28, 0x0, ...)
/tmp/sandbox/go/src/pkg/reflect/value.go:351 +0x40
main.TestFunc(0x10546120, 0xe)
/tmpfs/gosandbox-3642d986_9569fcc1_f443bbfb_73e4528d_c874f1af/prog.go:34 +0x240
Go playground recover from that panic, but your test program might not.
That is why I added to the test function above:
for _, fname := range funcNames {
defer func() {
if x := recover(); x != nil {
t.Error("TestFunc paniced for", fname, ": ", x)
}
}()
fmt.Println(fname)
That produces (see example) a much nicer output:
Func1
Func2
Func3
Func4
--- FAIL: TestFunc (0.00 seconds)
prog.go:48: Func2 should return positive value
prog.go:51: Func3 shouldn't err
prog.go:32: TestFunc paniced for Func4 : reflect: call of reflect.Value.Call on zero Value
FAIL