0

For testing purposes, I'm trying to mock the Rows.Scan method from the database/sql library. The signature looks like this:

func (r *Rows) Scan(dest ...interface{}) error

Many examples of how to use this show that you pass a pointer to a string (for example) to the Scan method and it will assign the value through the pointer.

var name string
rows.Scan(&name)

I've been trying to recreate something similar for my own understanding and eventually for mocking but it is not working.

func MockScan(args ...interface{}) {
    var s2 string
    fmt.Println(args[0])
    args[0] = &s2
    fmt.Println(args[0])
}

func main() {
    var s1 string
    fmt.Println(&s1)
    MockScan(&s1)
    fmt.Println(&s1)
}

Results in:

0xc000012950
0xc000012950
0xc000012960
0xc000012950

Where my initial string never takes on its new value. I understand that strings are immutable (although my true understanding of this concept is a little shakey) but somehow the actual Rows.Scan method manages to mutate the string.

David Buck
  • 3,752
  • 35
  • 31
  • 35
natsuki_2002
  • 24,239
  • 21
  • 46
  • 50
  • See possible duplicates: [Immutable string and pointer address](https://stackoverflow.com/questions/47352449/immutable-string-and-pointer-address/47352588#47352588); and [Immutability of string and concurrency](https://stackoverflow.com/questions/51249918/immutability-of-string-and-concurrency/51250031#51250031) – icza Oct 09 '19 at 15:18
  • `&s1` is a pointer to the local variable `s1`. Every time you do `&s1` you're going to get the same address because it's the same variable you're taking the address of. – Adrian Oct 09 '19 at 15:55

1 Answers1

5

args[0] = &s2

This does nothing outside of MockScan. You're changing what args[0] points to, you're not changing the thing pointed to by args[0].

You want to do something like

func MockScan(args ...interface{}) {
    // In real life, there would be some logic here to figure out what
    // kind of pointer we're dealing with, or some error handling to
    // handle the case where the user didn't pass a *string
    dst := args[0].(*string)
    *dst = "A new value"
}

func main() {
    var s1 string = "An old value"
    fmt.Println(s1)
    MockScan(&s1)
    fmt.Println(s1)
}

Note that if we printed &s1 in main before and after the call, it still wouldn't change. It's still the same variable, it's still at the same address. We've passed a pointer, &s1 into MockScan, so that MockScan can use that pointer to write a value into s1.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Worth mentioning here that a [Type Assertion](https://tour.golang.org/methods/15) was used to make sure we access args[0] as a string pointer. – SirDarius Oct 09 '19 at 15:26
  • Natski, see [this](https://en.wikipedia.org/wiki/Pointer_(computer_programming)) for more background—it's a crucial thing to grasp. – kostix Oct 09 '19 at 15:29
  • Thank you for your responses and answer. I was following this blog post which does the same thing but with structs (it just seems to work): https://goinbigdata.com/golang-pass-by-pointer-vs-pass-by-value/. Am I correct to assume that structs and strings are behaving differently between my example and the blog post? – natsuki_2002 Oct 09 '19 at 15:31
  • @natsuki_2002 regarding the linked post, if you do `p = &Person{firstName: "Bob"}` you get the same outcome as with the string pointer, so in this respect there's no difference between structs and strings. However the *selector* expression `s.f` is something different, here `s` can be a pointer to a struct and the language will still allow you to access the field `f` without having to first dereference the pointer, e.g. you don't have to do `(*s).f`, that's done automatically. [more here](https://golang.org/ref/spec#Selectors) – mkopriva Oct 09 '19 at 16:31