Participle is stating Unexpected Token "XXX" - go

I am playing with a participle to learn how to parse and I cannot determine why this is unexpected, code is here:
// nolint: golint, dupl
package main
import (
"fmt"
"io"
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
)
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"Comment", `^#[^\n]*`},
{"Ident", `^\w+`},
{"Int", `\d+`},
{"String", `("(\\"|[^"])*"|\S+)`},
{"EOL", `[\n\r]+`},
{"whitespace", `[ \t]+`},
})
type HTACCESS struct {
Directives []*Directive `##*`
}
type Directive struct {
Pos lexer.Position
ErrorDocument *ErrorDocument `##`
}
type ErrorDocument struct {
Code int `"ErrorDocument" #Int`
Path string `#String`
}
var htaccessParser = participle.MustBuild[HTACCESS](
participle.Lexer(htaccessLexer),
participle.CaseInsensitive("Ident"),
participle.Unquote("String"),
participle.Elide("whitespace"),
)
func Parse(r io.Reader) (*HTACCESS, error) {
program, err := htaccessParser.Parse("", r)
if err != nil {
return nil, err
}
return program, nil
}
func main() {
v, err := htaccessParser.ParseString("", `ErrorDocument 403 test`)
if err != nil {
panic(err)
}
fmt.Println(v)
}
From what I can tell, this seems to be correct, I expect 403 to be there, but I am not sure why it isn't recognizing it... Any help is appreciated!
Edit:
I changed my lexer to this:
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"dir", `^\w+`},
{"int", `\d+`},
{"str", `("(\\"|[^"])*"|\S+)`},
{"EOL", `[\n\r]+`},
{"whitespace", `\s+`},
})
And the error is gone, but it is still printing an empty array, not sure why. I am also not sure why using different values for the lexer fixed either....
Thank you!

I believe I found the issue, it is the order, Ident was finding numbers in my lexer via the \w tag, so this caused my integers to be marked as ident.
I found that I have to separate QuotedStrings and UnQuotedStrings otherwise unquoted strings was picking up integers. Alternatively I could ensure it only picks up non-numeric values, but that would miss things like stringwithnum2
Here is my solution
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"Comment", `(?i)#[^\n]*`},
{"QuotedString", `"(\\"|[^"])*"`},
{"Number", `[-+]?(\d*\.)?\d+`},
{"UnQuotedString", `[^ \t]+`},
{"Ident", `^[a-zA-Z_]`},
{"EOL", `[\n\r]+`},
{"whitespace", `[ \t]+`},
})
type ErrorDocument struct {
Pos lexer.Position
Code int `"ErrorDocument" #Number`
Path string `(#QuotedString | #UnQuotedString)`
}
This fixed my issue, because it now finds quoted strings, then looks for Numbers, then looks for unquoted strings.

Related

How can I compare read(1.proto) = read(2.proto) in Go(assuming there's just one message definition)?

Context: I'm trying to resolve this issue.
In other words, there's a NormalizeJsonString() for JSON strings (see this for more context:
// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func NormalizeJsonString(jsonString interface{}) (string, error) {
that allows to have the following code:
return structure.NormalizeJsonString(old) == structure.NormalizeJsonString(new)
but it doesn't work for strings that are proto files (all proto files are guaranteed to have just one message definition). For example, I could see:
syntax = "proto3";
- package bar.proto;
+ package bar.proto;
option java_outer_classname = "FooProto";
message Foo {
...
- int64 xyz = 3;
+ int64 xyz = 3;
Is there NormalizeProtoString available in some Go SDKs? I found MessageDifferencer but it's in C++ only. Another option I considered was to replace all new lines / group of whitespaces with a single whitespace but it's a little bit hacky.
To do this in a semantic fashion, the proto definitions should really be parsed. Naively stripping and/or replacing whitespace may get you somewhere, but likely will have gotchas.
As far as I'm aware the latest official Go protobuf package don't have anything to handle parsing protobuf definitions - the protoc compiler handles that side of affairs, and this is written in C++
There would be options to execute the protoc compiler to get hold of the descriptor set output (e.g. protoc --descriptor_set_out=...), however I'm guessing this would also be slightly haphazard considering it requires one to have protoc available - and version differences could potentially cause problems too.
Assuming that is no go, one further option is to use a 3rd party parser written in Go - github.com/yoheimuta/go-protoparser seems to handle things quite well. One slight issue when making comparisons is that the parser records meta information about source line + column positions for each type; however it is relatively easy to make a comparison and ignore these, by using github.com/google/go-cmp
For example:
package main
import (
"fmt"
"log"
"os"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/yoheimuta/go-protoparser/v4"
"github.com/yoheimuta/go-protoparser/v4/parser"
"github.com/yoheimuta/go-protoparser/v4/parser/meta"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
proto1, err := parseFile("example1.proto")
if err != nil {
return err
}
proto2, err := parseFile("example2.proto")
if err != nil {
return err
}
equal := cmp.Equal(proto1, proto2, cmpopts.IgnoreTypes(meta.Meta{}))
fmt.Printf("equal: %t", equal)
return nil
}
func parseFile(path string) (*parser.Proto, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return protoparser.Parse(f)
}
outputs:
equal: true
for the example you provided.

Join arguments into one string

I have this:
if t.FieldName != "" {
if t.FieldName != item.FieldName {
panic(errors.New("FieldName does not match, see: ", t.FieldName, item.FieldName))
}
}
that won't compile because errors.New takes one string arg. So I need to do something like:
panic(errors.New(joinArgs("FieldName does not match, see: ", t.FieldName, item.FieldName)))
How can I implement joinArgs, so that it concatenates all it's strings arguments into one string?
The XY problem is asking about your attempted solution rather than your actual problem: The XY Problem. Your real problem is formatting panic error messages.
This is the normal solution to your real problem:
package main
import "fmt"
func main() {
t := struct{ FieldName string }{FieldName: "a t.FieldName"}
item := struct{ FieldName string }{FieldName: "an item.FieldName"}
panic(fmt.Sprintf("FieldName does not match, see: %v %v", t.FieldName, item.FieldName))
}
Playground: https://play.golang.org/p/DaOlcqUgV_H
Output:
panic: FieldName does not match, see: a t.FieldName an item.FieldName
This seemed to work, not sure if it's optimal tho
func joinArgs(strangs ...string) string {
buffer := bytes.NewBufferString("")
for _, s := range strangs {
buffer.WriteString(s)
}
return buffer.String()
}

How to compare Go errors

I have an error value which when printed on console gives me Token is expired
How can I compare it with a specific error value? I tried this but it did not work:
if err == errors.New("Token is expired") {
log.Printf("Unauthorised: %s\n", err)
}
Declaring an error, and comparing it with '==' (as in err == myPkg.ErrTokenExpired) is no longer the best practice with Go 1.13 (Q3 2019)
The release notes mentions:
Go 1.13 contains support for error wrapping, as first proposed in the Error Values proposal and discussed on the associated issue.
An error e can wrap another error w by providing an Unwrap method that returns w.
Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.
To support wrapping, fmt.Errorf now has a %w verb for creating wrapped errors, and three new functions in the errors package ( errors.Unwrap, errors.Is and errors.As) simplify unwrapping and inspecting wrapped errors.
So the Error Value FAQ explains:
You need to be prepared that errors you get may be wrapped.
If you currently compare errors using ==, use errors.Is instead.
Example:
if err == io.ErrUnexpectedEOF
becomes
if errors.Is(err, io.ErrUnexpectedEOF)
Checks of the form if err != nil need not be changed.
Comparisons to io.EOF need not be changed, because io.EOF should never be wrapped.
If you check for an error type using a type assertion or type switch, use errors.As instead. Example:
if e, ok := err.(*os.PathError); ok
becomes
var e *os.PathError
if errors.As(err, &e)
Also use this pattern to check whether an error implements an interface. (This is one of those rare cases when a pointer to an interface is appropriate.)
Rewrite a type switch as a sequence of if-elses.
This answer is for Go 1.12 and earlier releases.
Define an error value in a library
package fruits
var NoMorePumpkins = errors.New("No more pumpkins")
Do not create errors with errors.New anywhere in the code but return the predefined value whenever error occurs and then you can do the following:
package shop
if err == fruits.NoMorePumpkins {
...
}
See io package errors for reference.
This can be improved by adding methods to hide the check implementation and make the client code more immune to changes in fruits package.
package fruits
func IsNoMorePumpkins(err error) bool {
return err == NoMorePumpkins
}
See os package errors for reference.
Try
err.Error() == "Token is expired"
Or create your own error by implementing the error interface.
It's idiomatic for packages to export error variables that they use so others can compare against them.
E.g. If an error would came from a package named myPkg and was defined as:
var ErrTokenExpired error = errors.New("Token is expired")
You could compare the errors directly as:
if err == myPkg.ErrTokenExpired {
log.Printf("Unauthorised: %s\n", err)
}
If the errors come from a third party package and that doesn't use exported error variables then what you can do is simply to compare against the string you get from err.Error() but be careful with this approach as changing an Error string might not be released in a major version and would break your business logic.
The error type is an interface type. An error variable represents any value that can describe itself as a string. Here is the interface's declaration:
type error interface {
Error() string
}
The most commonly-used error implementation is the errors package's unexported errorString type:
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
See this working code output (The Go Playground):
package main
import (
"errors"
"fmt"
"io"
)
func main() {
err1 := fmt.Errorf("Error")
err2 := errors.New("Error")
err3 := io.EOF
fmt.Println(err1) //Error
fmt.Printf("%#v\n", err1) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err2) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err3) // &errors.errorString{s:"EOF"}
}
output:
Error
&errors.errorString{s:"Error"}
&errors.errorString{s:"Error"}
&errors.errorString{s:"EOF"}
Also see: Comparison operators
Comparison operators compare two operands and yield an untyped boolean
value. In any comparison, the first operand must be assignable to the
type of the second operand, or vice versa.
The equality operators == and != apply to operands that are
comparable.
Pointer values are comparable. Two pointer values are equal if they
point to the same variable or if both have value nil. Pointers to
distinct zero-size variables may or may not be equal.
Interface values are comparable. Two interface values are equal if
they have identical dynamic types and equal dynamic values or if both
have value nil.
A value x of non-interface type X and a value t of interface type T
are comparable when values of type X are comparable and X implements
T. They are equal if t's dynamic type is identical to X and t's
dynamic value is equal to x.
Struct values are comparable if all their fields are comparable. Two
struct values are equal if their corresponding non-blank fields are
equal.
So:
1- You may use Error(), like this working code (The Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1.Error() == err2.Error() {
fmt.Println(err1.Error() == err2.Error()) // true
}
}
output:
true
2- Also you may compare it with nil, like this working code (The Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1 != nil {
fmt.Println(err1 == err2) // false
}
}
output:
false
3- Also you may compare it with exact same error, like this working code
(The Go Playground):
package main
import (
"fmt"
"io"
)
func main() {
err1 := io.EOF
if err1 == io.EOF {
fmt.Println("err1 is : ", err1)
}
}
output:
err1 is : EOF
ref: https://blog.golang.org/error-handling-and-go
It's being discouraged to compare errors by strings. Instead you should compare errors by value.
package main
import "errors"
var NotFound = errors.New("not found")
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
It is especially useful if you are library author and would like to export errors so users can act differently on different type of errors. Standard library does it as well.
Problem with this approach is that exported values can be changed by anyone as Go doesn't support immutable values. Nothing prevents you, though, to use string as an error and make it const.
package main
type CustomError string
func (ce CustomError) Error() string {
return string(ce)
}
const NotFound CustomError = "not found"
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
It is more verbose but safer approach.
You should first consider comparing errors by value, as described in other solutions with:
if errors.Is(err1, err2) {
// do sth
}
However in some cases the error returned from a function is a bit complex, e.g. an error is being wrapped multiple times, with a context being added to it in each function call like fmt.Errorf("some context: %w", err), and you may simply just want to compare the error message of two errors. In such cases you can do this:
// SameErrorMessage checks whether two errors have the same messages.
func SameErrorMessage(err, target error) bool {
if target == nil || err == nil {
return err == target
}
return err.Error() == target.Error()
}
func main() {
...
if SameErrorMessage(err1, err2) {
// do sth
}
}
Note that if you simply use
if err1.Error() == err2.Error() {
// do sth
}
You might face nil pointer dereference runtime error if either of err1 or err2 be nil.
To add to #wst 's answer, in some cases, the errors.Is(err, NotFound) approach may not work for reasons I am trying to figure out too. If someone knows, please let me know in the comments.
But I found a better approach to use it in the following way which was working for me:
if NotFound.Is(err) {
// do something
}
Where var NotFound = errors.New("not found") is an exported common error declared.
In my case, the solution was
if models.GetUnAuthenticatedError().Is(err) {
// Do something
}
I want to post one case where errors.Is could work well for custom errors with non-comparable values.
type CustomError struct {
Meta map[string]interface{}
Message string
}
func (c CustomError) Error() string {
return c.Message
}
var (
ErrorA = CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
)
func DoSomething() error {
return ErrorA
}
func main() {
err := DoSomething()
if errors.Is(err, ErrorA) {
fmt.Println("error is errorA")
} else {
fmt.Println("error is NOT errorA")
}
}
Output
error is NOT errorA
Playground
The reason is errors.Is checks whether the target is comparable or not
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
The comparable type in Go are
booleans, numbers, strings, pointers, channels, arrays of comparable types, structs whose fields are all comparable types
Since the Meta map[string]interface{} of CustomError is NOT comparable, so errors.Is checks failed.
One workaround is declare the ErrorA = &CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}} as pointer.

how to find, "invalid character ',' looking for beginning of value" error message

I have a short Go program that runs the go list -json command for several packages, stores the output of each run of the command in a json.RawMessage, appends each json.RawMessage into a slice of json.RawMessages, and then returns the result to the server after concatenating each of the json.RawMessages together and compacting the json. However, there is an error message that gets produced when I run json.Compact that I can't locate the source of. Googling this error message reveals that most people who seem to encounter it--whether it's for an invalid , or some other character--have a hard time finding the source of it.
invalid character ',' looking for beginning of value
The code with comments is available to view here on play.golang.org (although it won't run there) and also below.
Question: can you explain the source of this error and how to prevent it?
(Note, some of the packages were included just for testing purposes)
package main
import (
"expvar"
"encoding/json"
"bytes"
"fmt"
"github.com/go-martini/martini"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
"go/build"
"log"
"math/rand"
"net/http"
_ "net/http/pprof"
"os/exec"
)
type myType struct {
J []json.RawMessage
}
var pack map[string]string
type GoList struct {
Imports []string
}
type Import struct {
Dir string
ImportPath string
Name string
Target string
Standard bool
Root string
GoFiles []string
Imports []string
Deps []string
}
const contentTypeJSON = "application/json"
func main() {
http.HandleFunc("/importgraph", func(w http.ResponseWriter, r *http.Request) { importGraph(w, r) })
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Println("Inside handler")
fmt.Fprintf(w, "Hello world from my Go program!")
}
func importGraph(w http.ResponseWriter, r *http.Request) {
pack = make(map[string]string)
var t myType
cmd := exec.Command("go", "list", "-json")
stdout, err := cmd.Output()
if err != nil {
println(err.Error())
return
}
var list GoList
err = json.Unmarshal(stdout, &list)
for _, d := range list.Imports {
//get the imports for each of the packages listed by go list -json
t.imports(d)
}
var buff bytes.Buffer
//concatenate the separate json.RawMessages together into json
buff.WriteByte('[')
for i, j := range t.J {
if i != 0 {
buff.WriteByte(',')
}
buff.Write([]byte(j))
}
buff.WriteByte(']')
var buffer bytes.Buffer
if err := json.Compact(&buffer, buff.Bytes()); err != nil {
println(err.Error()) //error message: invalid character ',' looking for beginning of value
return
}
w.Header().Set("Content-Type", contentTypeJSON)
w.Write(buffer.Bytes())
}
func (myObj *myType) imports(pk string) error {
cmd := exec.Command("go", "list", "-json", pk)
stdout, _ := cmd.Output()
pack[pk] = pk
var deplist Import
json.Unmarshal(stdout, &deplist)
var newj json.RawMessage
json.Unmarshal(stdout, &newj)
myObj.J = append(myObj.J, newj)
for _, imp := range deplist.Imports {
if _, ok := pack[imp]; !ok {
myObj.imports(imp) //recursive call to get the imports of the imports etc
}
}
return nil
}
First, as has been commented, are you sure you can't use
the go/build package directly rather than running go list?
I Wouldn't use println (or fmt.Println) inside HTTP handlers. It's much better to use log.Println and/or get the error into the ResponseWriter. Also, it's a good idea to wrap your ListenAndServe call with log.Fatal.
When printing/logging error values you can just use err, no need to have err.Error().
Further, when you actually want to do something more detailed than just reporting/logging the error message you can look at it's type and other info. For example, log.Printf("verbose error info: %#v", err) gives:
&json.SyntaxError{msg:"invalid character ',' looking for beginning of value", Offset:0}
I tried this because I know the json package returns various error types with additional info and I was hoping the offset value would be of help. If it had been then something like this might have been helpful:
if err := json.Compact(…) {
if err != nil {
log.Println("json.Compact:", err)
if serr, ok := err.(*json.SyntaxError); ok {
log.Println("Occurred at offset:", serr.Offset)
// … something to show the data in buff around that offset …
}
}
}
But offset zero isn't helpful :(
So although this doesn't identify you problem hopefully
it can be of some help to your further investigation.
Edit:
So after adding:
log.Println("Write file:", ioutil.WriteFile("data.json", buff.Bytes(), 0600))
to the above error handling block I then ran a JSON validator on the resultant file and it identified this piece:
"XTestImports": [
"io",
"log",
"net"
]
},,{
"Dir": "/usr/local/go/src/mime",
"ImportPath": "mime",
"Name": "mime",
Note the double ,,.
That should tell you whete the error in your code is.
But if not, you need to skip empty entries, either when processing t.J or when you build it. The later is better and just involves:
if len(newj) > 0 {
myObj.J = append(myObj.J, newj)
}
(where btw you don't check for errors from json.Unmarshal so it's not clear if that is supposed to ever be empty or if it's empty due to a preceeding error. Never ignore error returns!)
I also encountered the same error message in a Go program, but the error message was within the HTTP response error, in HTML format when my HTTP response parser expected JSON.
For me, the solution was to change my request to include setting the Content-Type header to application/json. How you do this depends on which http client library you happen to be using; if you have access to the http.Header core type, you can set the header with .Set(...).
I realize the scope of this fix for me may not apply to the original question, but I came here first after googling and thought this would help others, since the message was not particularly obvious at first glance. The hint is that the invalid < character is the first HTML character in the error/response, which is likely the result of the request type not being set to application/json, thus the server responds with a non JSON response.
For me the issue was I was trying to parse the already parsed JSON.
I was also facing this error "invalid character 'N' looking for beginning of value".
This error was coming while "unmarshalling the non-json response into a json". I was expecting a json response, so wrote go code to unmarshal it into a json. But, due to URL change, the response that I was getting was a text ie. "404 Not found" error, which cannot be unmarshalled into a json.
"invalid character 'N' looking for beginning of value"
So, to summarise, this error appears when we are trying to unmarshal a non-json response (text/html/xml) into json.
Reason for this eerie error message is :
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
https://golang.org/src/encoding/json/decode.go
In my case I saved my json as string then parsed it by :
stringData = JSON.parse(myJsonString)
I also had the same error another time using gin-context-ShouldBind() (https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind) and mapping my json to go object:
error was because it needs a json as string, so I used : JSON.stringify(jsonObject) when sending my request from front-end part.
And in case someone has the same problem as me, I needed to call JSON.stringify on my post data.
I encountered a similar problem with my error message being:
invalid character 'I' looking for beginning of value
In my case, i was trying to decode BSON using json.Unmarshal. Json doesn't recognize the ISODate type, which caused this error.
I had a similar issue. For me I omitted the first letter of my authorization token. So instead of
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJhcGhhZWxuZ0BlbWFpbC5jb20iLCJleHAiOjE2MTM5NTQzMjB9.yPGC937VNAF8Qg05Z1x3fZ3zu_MUs-cA_Iag5-4RcJE"
I used this
"yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJhcGhhZWxuZ0BlbWFpbC5jb20iLCJleHAiOjE2MTM5NTQzMjB9.yPGC937VNAF8Qg05Z1x3fZ3zu_MUs-cA_Iag5-4RcJE"

Optional Parameters in Go?

Can Go have optional parameters? Or can I just define two different functions with the same name and a different number of arguments?
Go does not have optional parameters nor does it support method overloading:
Method dispatch is simplified if it
doesn't need to do type matching as
well. Experience with other languages
told us that having a variety of
methods with the same name but
different signatures was occasionally
useful but that it could also be
confusing and fragile in practice.
Matching only by name and requiring
consistency in the types was a major
simplifying decision in Go's type
system.
A nice way to achieve something like optional parameters is to use variadic args. The function actually receives a slice of whatever type you specify.
func foo(params ...int) {
fmt.Println(len(params))
}
func main() {
foo()
foo(1)
foo(1,2,3)
}
You can use a struct which includes the parameters:
type Params struct {
a, b, c int
}
func doIt(p Params) int {
return p.a + p.b + p.c
}
// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})
The main advantage over an ellipsis (params ...SomeType) is that you can use the param struct with different parameter types.
For arbitrary, potentially large number of optional parameters, a nice idiom is to use Functional options.
For your type Foobar, first write only one constructor:
func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
fb := &Foobar{}
// ... (write initializations with default values)...
for _, op := range options{
err := op(fb)
if err != nil {
return nil, err
}
}
return fb, nil
}
where each option is a function which mutates the Foobar. Then provide convenient ways for your user to use or create standard options, for example :
func OptionReadonlyFlag(fb *Foobar) error {
fb.mutable = false
return nil
}
func OptionTemperature(t Celsius) func(*Foobar) error {
return func(fb *Foobar) error {
fb.temperature = t
return nil
}
}
Playground
For conciseness, you may give a name to the type of the options (Playground) :
type OptionFoobar func(*Foobar) error
If you need mandatory parameters, add them as first arguments of the constructor before the variadic options.
The main benefits of the Functional options idiom are :
your API can grow over time without breaking existing code, because the constuctor signature stays the same when new options are needed.
it enables the default use case to be its simplest: no arguments at all!
it provides fine control over the initialization of complex values.
This technique was coined by Rob Pike and also demonstrated by Dave Cheney.
Neither optional parameters nor function overloading are supported in Go. Go does support a variable number of parameters: Passing arguments to ... parameters
No -- neither. Per the Go for C++ programmers docs,
Go does not support function
overloading and does not support user
defined operators.
I can't find an equally clear statement that optional parameters are unsupported, but they are not supported either.
You can pass arbitrary named parameters with a map. You will have to assert types with "aType = map[key].(*foo.type)" if the parameters have non-uniform types.
type varArgs map[string]interface{}
func myFunc(args varArgs) {
arg1 := "default"
if val, ok := args["arg1"]; ok {
arg1 = val.(string)
}
arg2 := 123
if val, ok := args["arg2"]; ok {
arg2 = val.(int)
}
fmt.Println(arg1, arg2)
}
func Test_test() {
myFunc(varArgs{"arg1": "value", "arg2": 1234})
}
Go doesn’t support optional parameters , default values and function overloading but you can use some tricks to implement the same.
Sharing one example where you can have different number and type of arguments in one function. It’s a plain code for easy understanding you need to add error handling and some logic.
func student(StudentDetails ...interface{}) (name string, age int, area string) {
age = 10 //Here Age and area are optional params set to default values
area = "HillView Singapore"
for index, val := range StudentDetails {
switch index {
case 0: //the first mandatory param
name, _ = val.(string)
case 1: // age is optional param
age, _ = val.(int)
case 2: //area is optional param
area, _ = val.(string)
}
}
return
}
func main() {
fmt.Println(student("Aayansh"))
fmt.Println(student("Aayansh", 11))
fmt.Println(student("Aayansh", 15, "Bukit Gombak, Singapore"))
}
So I feel like I'm way late to this party but I was searching to see if there was a better way to do this than what I already do. This kinda solves what you were trying to do while also giving the concept of an optional argument.
package main
import "fmt"
type FooOpts struct {
// optional arguments
Value string
}
func NewFoo(mandatory string) {
NewFooWithOpts(mandatory, &FooOpts{})
}
func NewFooWithOpts(mandatory string, opts *FooOpts) {
if (&opts) != nil {
fmt.Println("Hello " + opts.Value)
} else {
fmt.Println("Hello")
}
}
func main() {
NewFoo("make it work please")
NewFooWithOpts("Make it work please", &FooOpts{Value: " World"})
}
Update 1:
Added a functional example to show functionality versus the sample
You can encapsulate this quite nicely in a func similar to what is below.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Println(prompt())
}
func prompt(params ...string) string {
prompt := ": "
if len(params) > 0 {
prompt = params[0]
}
reader := bufio.NewReader(os.Stdin)
fmt.Print(prompt)
text, _ := reader.ReadString('\n')
return text
}
In this example, the prompt by default has a colon and a space in front of it . . .
:
. . . however you can override that by supplying a parameter to the prompt function.
prompt("Input here -> ")
This will result in a prompt like below.
Input here ->
You could use pointers and leave them nil if you don't want to use them:
func getPosts(limit *int) {
if optParam != nil {
// fetch posts with limit
} else {
// fetch all posts
}
}
func main() {
// get Posts, limit by 2
limit := 2
getPosts(&limit)
// get all posts
getPosts(nil)
}
Go language does not support method overloading, but you can use variadic args just like optional parameters, also you can use interface{} as parameter but it is not a good choice.
I ended up using a combination of a structure of params and variadic args. This way, I didn't have to change the existing interface which was consumed by several services and my service was able to pass additional params as needed. Sample code in golang playground: https://play.golang.org/p/G668FA97Nu
I am a little late, but if you like fluent interface you might design your setters for chained calls like this:
type myType struct {
s string
a, b int
}
func New(s string, err *error) *myType {
if s == "" {
*err = errors.New(
"Mandatory argument `s` must not be empty!")
}
return &myType{s: s}
}
func (this *myType) setA (a int, err *error) *myType {
if *err == nil {
if a == 42 {
*err = errors.New("42 is not the answer!")
} else {
this.a = a
}
}
return this
}
func (this *myType) setB (b int, _ *error) *myType {
this.b = b
return this
}
And then call it like this:
func main() {
var err error = nil
instance :=
New("hello", &err).
setA(1, &err).
setB(2, &err)
if err != nil {
fmt.Println("Failed: ", err)
} else {
fmt.Println(instance)
}
}
This is similar to the Functional options idiom presented on #Ripounet answer and enjoys the same benefits but has some drawbacks:
If an error occurs it will not abort immediately, thus, it would be slightly less efficient if you expect your constructor to report errors often.
You'll have to spend a line declaring an err variable and zeroing it.
There is, however, a possible small advantage, this type of function calls should be easier for the compiler to inline but I am really not a specialist.
Another possibility would be to use a struct which with a field to indicate whether its valid. The null types from sql such as NullString are convenient. Its nice to not have to define your own type, but in case you need a custom data type you can always follow the same pattern. I think the optional-ness is clear from the function definition and there is minimal extra code or effort.
As an example:
func Foo(bar string, baz sql.NullString){
if !baz.Valid {
baz.String = "defaultValue"
}
// the rest of the implementation
}

Resources