无效的内存地址或使用sql.DB的nil指针取消引用

I'm learning Go at the moment and trying to make a little SQL-toolset:

type DBUtils struct {
  User string
  Password string
  Host string
  Database string
  Handle *sql.DB
}

func (dbUtil DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf("%v", dbUtil)
}

func (dbUtil DBUtils) Close() {
  dbUtil.Handle.Close()
}

func (dbUtil DBUtils) GetString(what string, from string, where string, wherevalue string) string {
  var username string

  fmt.Printf("%v", dbUtil)

  stmtOut, err := dbUtil.Handle.Prepare("SELECT " + what + " FROM " + from + " WHERE " + where + " = " + wherevalue)
  if err != nil {
      panic(err.Error()) // proper error handling instead of panic in your app
  }

  err = stmtOut.QueryRow(1).Scan(&username)

  return username
}

So when using this with the following code:

db := databaseutils.DBUtils{"root", "root", "127.0.0.1:3306", "gotest", nil}    
db.Connect() // I get: {root root 127.0.0.1:3306 gotest 0xc42019d600}
fmt.Printf("%v", db) // I get {root root 127.0.0.1:3306 gotest <nil>}
x := db.GetString("username", "users", "id", "1") // Doesn't work: panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(x)

For me it seems like my DB handle isn't saved properly? Does anyone have an idea - I'm pretty new to go and there are many things looking different to PHP, JS, C++ etc.

Thanks in advance!

Your Connect method is not changing the state of the object you're calling the method on. You're working on a copy of the type. If you want a method to change the object itself, you'll have to define it on a pointer:

func (dbUtil *DBUtils) Connect() {
//instead of
func (dbUtil DBUtils) Connect() {

If you're familiar with C or C++, your current method works similarly to something like:

void connect_db(struct db_utils db_util)
{}

When you call a function like that, you're creating a copy of the argument, and push that on to the stack. The connect_db function will work with that, and after it returns, the copy is deallocated.

compare it to this C code:

struct foo {
    int bar;
};
static
void change_copy(struct foo bar)
{
    bar.bar *= 2;
}

static
void change_ptr(struct foo *bar)
{
    bar->bar *= 2;
}

int main ( void )
{
    struct foo bar = {10};
    printf("%d
", bar.bar);//prints 10
    change_copy(bar);//pass by value
    printf("%d
", bar.bar);//still prints 10
    change_ptr(&bar);
    printf("%d
", bar.bar);//prints 20
    return 0;
}

The same thing happens in go. The object on which you define the method can only change state of the instance if it has access to the instance. If not, it can't update that part of the memory.

In case you're wondering, this method doesn't need to be defined on the pointer type:

func (dbUtil DBUtils) Close() {
  dbUtil.Handle.Close()
}

The reason for this being that DBUtils.Handle is a pointer type. A copy of that pointer will always point to the same resource.

I will say this, though: given that you're essentially wrapping the handle, and you're exposing the Connect and Close methods, the member itself really shouldn't be exported. I'd change it to lower-case handle

You are right about your DB handle not being saved. You are defining your methods on a value receiver. Therefore you receive a copy inside the Connect method and modify said copy. This copy is then dropped at the end of the Connect method.

You have to define your method on a pointer to your structure:

func (dbUtil *DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf("%v", dbUtil)
}

For further information see https://golang.org/doc/faq#methods_on_values_or_pointers.

Note: I noticed your usage of QueryRow. It accepts arguments for parameters in your prepare statement. You have no parameters there, so I think you should not pass any parameters. You should also check the Scan result for errors (see https://golang.org/pkg/database/sql/#Stmt).