0

I am trying to get all documents from a Firestore database and things were working fine.

But then I decided to make the context and client variable global, so that I won't have to deal with passing them as parameters everytime.

Things broke after that.

The error I get is: panic: runtime error: invalid memory address or nil pointer dereference

and according to the stack trace, it occurs when I try to: client.Collection("dummy").Documents(ctx)

What can I do to resolve this?

And how can I efficiently work with global variables in my case?

My code for reference:

package main

import (
    "context"
    "fmt"
    "log"

    "cloud.google.com/go/firestore"
    firebase "firebase.google.com/go"
    "google.golang.org/api/iterator"
    "google.golang.org/api/option"
)

var (
    ctx    context.Context
    client *firestore.Client
)

func init() {
    ctx := context.Background()
    keyFile := option.WithCredentialsFile("serviceAccountKey.json")
    app, err := firebase.NewApp(ctx, nil, keyFile)
    if err != nil {
        log.Fatalln(err)
    }

    client, err = app.Firestore(ctx)
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println("Connection to Firebase Established!")
}

func getDocuments(collectionName string) {
    iter := client.Collection("dummy").Documents(ctx)

    for {
        doc, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            log.Fatalf("Failed to iterate: %v", err)
        }
        fmt.Println(doc.Data()["question"])
    }
}

func main() {
    getDocuments("dummy")
    defer client.Close()
}

Window Pane
  • 55
  • 1
  • 7
  • 1
    The concept of a "global context" makes no sense, and is almost certainly a design flaw.[pkg/context](https://golang.org/pkg/context/): "Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values." Global is pretty much the opposite of request-scoped. – Adrian Jul 27 '20 at 13:38
  • Does this answer your question? [How to use global var across files in a package?](https://stackoverflow.com/questions/34195360/how-to-use-global-var-across-files-in-a-package) – Peter Jul 27 '20 at 13:42
  • @Adrian it might be scoped wrt the request but it can be globally scoped wrt the package. – Window Pane Jul 27 '20 at 13:45
  • 1
    It can be, but it shouldn't be. The `context` package provides for *request-scoped* contexts. That's what they're for, and that's how everything that takes a `context` expects them to work. Using them in a different manner is a design flaw. – Adrian Jul 27 '20 at 14:11
  • aah, I understand your point now. Thanks for the heads up :) – Window Pane Jul 27 '20 at 15:36

1 Answers1

4

You get that error because you never assign anything to the package level ctx variable, so it remains nil.

Inside init() you use short variable declaration which creates a local variable:

ctx := context.Background()

If you change to to simple assignment, it will assign a value to the existing, package level ctx variable:

ctx = context.Background()

Although using "global" variables to store something that's not global is bad practice. You should just pass ctx where it's needed.

icza
  • 389,944
  • 63
  • 907
  • 827
  • I feel so silly, failing to spot that colon. I would have also had to pass ctx and client, so won't the code become unreadable if I pass multiple params? – Window Pane Jul 27 '20 at 13:39
  • 1
    @Sherly `client` may be stored in a global variable or some place having application scope because it usually only needs to be created once, like database connections. Context is not like that. – icza Jul 27 '20 at 13:50
  • Sorry if I am asking too much, but why is Context not like that? If you have some good reading on this, I would love to check it out! – Window Pane Jul 27 '20 at 14:02
  • @Sherly Database connections should be created once so the driver can leverage connection pooling. Context carries deadlines and cancellation signals, which may vary at each "point" in your code. You may use 3 sec timeout for a db query, then 10 seconds for an external API call etc. – icza Jul 27 '20 at 14:10
  • 2
    @Sherly ...and you may have multiple, concurrent queries / calls running at the same time, and you may want to cancel only some of them (but not all). Having a single context you could only cancel all at the same time. – icza Jul 27 '20 at 14:23