0

I have the following function which is supposed to retrieve a single row from my ideas table:

func (s *IdeaService) GetIdea(id int64) (*ideaservice.Idea, error) {
    stmt, err := s.DB.Prepare("SELECT id, name, description, created_on, last_updated FROM ideas WHERE id = $1")
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    var idea *ideaservice.Idea
    err = stmt.QueryRow(id).Scan(&idea.ID, &idea.Name, &idea.Description, &idea.CreatedOn, &idea.LastUpdated)
    return idea, err
}

When i run this in a test, the following error occurs:

--- FAIL: TestGetIdea (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x65be98]

I can't seem to figure out the problem. I believe I have instantiated the idea var correctly as a pointer, which I then assign values to in the Scan method. Does anyone have any suggestions?

Mike Hawkins
  • 2,144
  • 5
  • 24
  • 42
  • 1
    `var idea *ideaservice.Idea` this does not initialize the pointer, therefore the `idea` variable is `nil`. Use `var idea = new(ideaservice.Idea)`, or `idea := &ideaservice.Idea{}` to properly initialize your variable. – mkopriva Apr 17 '20 at 13:10
  • 1
    When declaring variables using `var v T`, the variable `v` is initialized to the zero-value of type `T`, if type `T` is a pointer type whose zero-value is `nil` then `v` will be also `nil`. For comparison, doing `var idea ideaservice.Idea` (assuming `Idea` is a struct) is completely fine, because the zero-value of struct types is the value of a struct with all it's fields initialized to their zero value. https://golang.org/ref/spec#The_zero_value – mkopriva Apr 17 '20 at 13:12
  • Could an alternative solution be to declare the var as such: `var idea ideaservice.Idea` and then return the reference to the var from the func? It seems to work fine code-wise, but I'm curious as to whether or not this is equivalent to your proposal – Mike Hawkins Apr 17 '20 at 13:15
  • Yes, i've just added that to the previous comment. And you can still return a pointer using `return &idea, err`. – mkopriva Apr 17 '20 at 13:16
  • 1
    You should probably check the error with `ErrNoRows` and return nil if it matches. – Stéphane Jeandeaux Apr 17 '20 at 13:19
  • @StéphaneJeandeaux I have added a switch statement to check the error. Thanks. – Mike Hawkins Apr 17 '20 at 13:25

1 Answers1

0
func (s *IdeaService) GetIdea(id int64) (*ideaservice.Idea, error) {
    stmt, err := s.DB.Prepare("SELECT id, name, description, created_on, last_updated FROM ideas WHERE id = $1")
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    var idea *ideaservice.Idea
    err = stmt.QueryRow(id).Scan(&idea.ID, &idea.Name, &idea.Description, &idea.CreatedOn, &idea.LastUpdated)
    return idea, err
}

Could you replace $1 with ? and see if it works? In stmt.QueryRow you're passing the id. Like this:

stmt, err := s.DB.Prepare("SELECT id, name, description, created_on, last_updated FROM ideas WHERE id = ?")

If it doesn't work, then add this block:

switch {
    case err == sql.ErrNoRows:
        log.Fatalf("no user with id %d", id)
    case err != nil:
        log.Fatal(err)
    default:
        log.Println("ok!")
    }

UPDATE: Didn't see the comments on the question. Yes, he's correct as well. Do fix them as well.

And check where the err is trapped (in which case).

shmsr
  • 3,802
  • 2
  • 18
  • 29