Pass []string to a function that expects a variadic parameter - go

In order to don't repeat my self over and over I wanted to create a function that handles running some commands.
func runCommand(name string, arg ...string) error {
cmd := exec.Command(name, arg)
if err := cmd.Run(); err != nil {
return err
} else {
return nil
}
}
Once I try to run this I get the following error:
cannot use arg (type []string) as type string in argument to exec.Command
I had a look into the implementation of the os.Command and it looks that the function signature is exact what I supply.
Internally a []string should be the same as variadic parameter but for the compiler it seems not.
Is there a way to pass the variadic parameter into the Command?

You expand the []string with another ...
cmd := exec.Command(name, arg...)
From the language spec on Passing arguments to ... parameters
If the final argument is assignable to a slice type []T, it may be
passed unchanged as the value for a ...T parameter if the argument is
followed by .... In this case no new slice is created.
Given the slice s and call
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)
within Greeting, who will have the same value as s with the same underlying array.

Related

first argument to append must be a slice; have untyped nil

I'm a newbie Go programmer confused by the below behaviour. I expected the program to fail at t2 with error
first argument to append must be a slice; have untyped nil
but Go is happy when passing in nil as first parameter to append when it's a parameter to a method?
package main
import "fmt"
type Thing struct {
data []string
}
func NewThing(extra []string) Thing {
return Thing{
data: append(extra, "b"),
}
}
func main() {
t := NewThing([]string{"a"})
fmt.Println(t.data) // [a b]
t2 := NewThing(nil)
fmt.Println(t2.data) // [b]
//_ = Thing{ // build failed: first argument to append must be a slice; have untyped nil
// data: append(nil, "b"),
//}
}
Playground: https://go.dev/play/p/Cxi7fRHu3Wi
Is this just a convenience feature or am I understanding this wrong?
It is a convenience that the capacity and length of a nil slice is zero. From there, append works as normal when passed a nil slice.
The call append(nil, "b") does not compile because nil has no type. The compiler cannot determine the type of the result because the slice argument does not have a type.
The call append(extra, "b") works when extra is nil because extra is a typed value (a []string). Given the slice type, the compiler knows that the result is []string and that the appended elements must be assignable to string.
The call append([]string(nil), "b") also works because the expression []string(nil) is a typed value.

How to unpack the slice of string and pass them to exec.Command

I'd like to write a function that puts two slices of strings as arguments in a shell command. What I'm trying to do is to unpack the 2 slices, and pass them to exec.Command.
image_tag := "mydocker/mytag"
func buildDockerContainer(dockerArgs []string, otherArgs []string) {
cmd := exec.Command("docker", "run", "-d", dockerArgs..., image_tag, otherArgs...)
}
However, when writing this, Goland keeps giving me syntax error:
Invalid use of '...', the corresponding parameter is non-variadic
I know I can do the following:
cmdToRun := []string{"run", "-d"}
cmdToRun = append(cmdToRun, append(append(dockerArgs, image_tag), otherArgs...)...)
cmd := exec.Command("docker", cmdToRun...)
But is there a more elegant way that I can do all these inline?
Use append:
args:= append(append(append([]string{"run","-d"},dockerArgs...),image_tag),otherArgs...)
cmd := exec.Command("docker", args...)
exec.Command(name string, arg ...string) behave same as append function in this case. the problem is that if the first arg is string, later it expect only string, but if you pass instead only slice that is fine.
https://go.dev/play/p/TBi3nsKue5n
and don't forget the ... at the end of a slice, when the function expect a string and you pass []string

How do you pass a slice of *interface{} as arguments?

I want to use Scan() in package sql, but the number of columns, and hence the number of arguments, will change at runtime. This is the signature of Scan():
func (rs *Rows) Scan(dest ...interface{}) error
According to the documentation, *interface{} is one of the types accepted by Scan(). So I want to create a slice of []*interface{} and that expand as arguments.
This is what I thought would work:
func query(database *sql.DB) {
rows, _ := database.Query("select * from testTable")
for rows.Next() {
data := make([]*interface{}, 2)
err := rows.Scan(data...) // Compilation error
fmt.Printf("%v%v\n", *data[0], *data[1])
if err != nil {
fmt.Println(err.Error())
}
}
}
Compilation fails with cannot use data (type []*interface {}) as type []interface {} in argument to rows.Scan. I thought that data... would expand to &data[0], &data[1], but apparently not. I don't understand the error message. *interface{} is compatible with interface{}, so why can't I expand the slice of pointers to interface types?
This works:
func query(database *sql.DB) {
rows, _ := database.Query("select * from testTable")
for rows.Next() {
data := make([]*interface{}, 2)
err := rows.Scan(&data[0], &data[1]) // Only changed this line
fmt.Printf("%v%v\n", *data[0], *data[1]) // Outputs "[48][116 101 120 116]"
if err != nil {
fmt.Println(err.Error())
}
}
}
I can't use this however, because the number of columns is unknown at compile time. How can I write this code so that I can pass a variable number of *interface{} to rows.Scan()?
First, you must not use []*interface{} slice of pointers to interface rather than []interface{} where the interfaces are pointers. []*interface{} is different from []interface{}. Just create a slice of interfaces where each element is a pointer to a concrete type.
Here is a snippet how you would do this.
var x int
var s string
data := []interface{}{&x, &s}
rows.Scan(data...)
Note on the use of the ... spread operator.
Here are some related questions that will explain a bit more:
golang: slice of struct != slice of interface it implements?
Cannot convert []string to []interface {}
If you really want to pass a []*interface{} (perhaps you don't know the concrete types of the output) you must first wrap each *interface{} in a interface{}:
values := make([]interface{}, columnsCount)
for i := range values {
values[i] = new(interface{})
}
Individual values passed into a ...interface{} parameter are automatically wrapped in a interface{}, but just like []int... won't satisfy ...interface{}, neither will []*interface{}....

How to pass variable ids to statement.Query() in golang?

I have this query in postgres which queries 1 or n users based on the parameters passed:
select name, phone from clients where id in ('id1','id2')
Now when I try to use this at golang I'm having problems approaching how to pass this type of variable arguments to the statement.Query() function:
ids := []string{"0aa6c0c5-e44e-4187-b128-6ae4b2258df0", "606b0182-269f-469a-bb29-26da4fa0302b"}
rows, err := stmt.Query(ids...)
This throws error: Cannot use 'ids' (type []string) as type []interface{}
When I check in source code query it can receive many variables of type interface:
func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
return s.QueryContext(context.Background(), args...)
}
If I do this manually it works:
rows, err := stmt.Query("0aa6c0c5-e44e-4187-b128-6ae4b2258df0", "606b0182-269f-469a-bb29-26da4fa0302b")
But of course I need the args to be 1 or many more, and dynamically generated.
I'm using Sqlx lib.
As we can see on the Query() method scheme and also from the error message, the method requires an argument in []interface{} type.
func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
return s.QueryContext(context.Background(), args...)
}
In your code, the ids variable hold []string data. Change it to []interface{} so it'll meet Query() requirements, then it'll work.
ids := []interface{}{
"0aa6c0c5-e44e-4187-b128-6ae4b2258df0",
"606b0182-269f-469a-bb29-26da4fa0302b",
}
rows, err := stmt.Query(ids...)

Golang pass nil as optional argument to a function?

In Golang, http.NewRequest has a specification like this:
func NewRequest(method, urlStr string, body io.Reader) (*Request, error)
However, I can pass nil as the body option if I don't want to pass the body to an io.Reader object, like this:
req, err := http.NewRequest("GET", "http://www.blahblah.org", nil)
How do I implement this functionality in my code? I have a function that I want to pass an optional string value so that it can page through API results however if I pass a nil to the string input I get this:
./snippets.go:32: cannot convert nil to type string
The parameters for my function look like this:
func getChallenges(after string) ([]challenge, string, error)
Go does not have "optional" arguments as a generally understood concept in other languages; nil is just the zero value for an interface (io.Reader in this case).
The equivalent zero value for a string is an empty string:
getChallenges("")
If you want to accept 0 or more of the same argument type, you use the variadic syntax:
func getChallenges(after ...string) ([]challenge, string, error)
You can modify you function to receive pointer value, like this:
func getChallenges(after *string) ([]challenge, string, error)
Then you can pass nil as an argument to it. But don't forget to check after for nil value inside your function before dereferencing it, or you will get a nil pointer exception:
func getChallenges(after *string) ([]challenge, string, error) {
if after == nil {
// No value specified
} else {
fmt.Printf("After: %s\n", *after) // Note pointer dereferencing with "*"
}
// ...
}
Another option:
Just use two functions:
func getChallenges(after string) {}
func getAllChallenges() {
return getChallenges(/* some default value here */)
}
you can use ellipse operator to send the optional parameters.. don't pass anything in optional parameter and check the length of parameter.
it should solve your problem
func foo(params ...int) {
fmt.Println(len(params))
}
func main() {
foo()
foo(1)
foo(1,2,3)
}
Maybe wrap it in a struct?
type NilableString struct {
value string;
}
You can use reflect.
In fact io.Reader is a interface.
So you can define signature like func getChallenges(after interface{}) ([]challenge, string, error)
interface{} is a empty interface, that is interface for anything.
But I suggest you use syntax args... to pass slice , refer to fmt.Printf implementation for usage, because if you pass no string the slice len is 0 and
this will avoid the reflect which I think too heavy for your function.

Resources