试图掌握Go的指针

I've written a small snippet to recursively walk a directory and return a slice of files ([]string) based on extension. It seems to work, but I cannot fully get the idea behind pointers and how to properly use it.

package main

import (
    "path/filepath"
    "io/ioutil"
    "fmt"
)

// aggregator slice should hold the result (a slice of filepaths)
// dir is the current directory being listed
// exts is a slice of extensions that should be included in the result
func recurse(aggregator *[]string, dir string, exts *[]string) []string {
    files, _ := ioutil.ReadDir(dir)
    for _, file := range files {
        // current filepath
        path := filepath.Join(dir, file.Name())

        // if directory, recursively analyze it
        if file.IsDir() {
            *aggregator = recurse(aggregator, path, exts)

        // else check if file is of proper extension and add it to aggregator
        } else {
            for _, ext := range *exts {
                if (filepath.Ext(path) == ext) {
                    *aggregator = append(*aggregator, path)
                    break
                }
            }
        }
    }
    return *aggregator
}

func getTemplates(templatesDir string, templatesExtensions ...string) {
    templates := recurse(&[]string{}, templatesDir, &templatesExtensions)

    // for testing purposes just print filepaths
    for _, t := range templates {
        fmt.Printf("%s
", t)
    }
}


func main() {
    getTemplates("./templates", ".tmpl", ".html")
}

The main question is using *aggregator (aggregator *[]string), &[]string{} and *exts (exts *[]string). I come from javascript world where every object is basically a pointer and you can only copy objects and arrays explicitly. Here, on the other hand, it seems that if I didn't use pointers (*aggregator, etc.), these objects would get copied on each function iteration.

Am I correct in this approach or am I missing something?

Though this visualization is not specifically about Go (it's about Java) yet it's a perfect one to actually visualize the usage of pointers and values (1): enter image description here

As you see a pointer does not actually contains any data, but it points to the place where data resides. So any modifications that are made on that data via a pointer, is actually getting performed on the data itself. But the data is not necessarily resides where our pointer is being used.

There are difference situations when we might need pointers. For example when you want to modify the actual values in one specific place (and not pass those values around) or when your data is too big that the cost would be just too high to send the actual content around. You can use a/some pointer to this big data and everybody (every function for example) that has a pointer to that data, can read it or modify it. And as just we said, we can have as many pointers to the same data as needed. So there may be many pointers to just the same, one data. The value of these pointers but are same; which is the address of the source data (object).

Forgot to add slices in Go would get passed by pointer/reference, but you can not modify the slice (add/remove items). You can modify it's items though. If you need to modify the slice itself, then you have to use a pointer to the slice. Otherwise if your just passing it as a container of some values, you do not need to worry because they won't get copied everytime.

(1) Source

In go slices are reference types, along with maps and pointers (I'm pretty sure strings are too don't qoute me on it though :) see here for a discussion on this). So these particular types are automatically passed by reference. So the actual variable itself evaluates as a pointer where the value would be the reference address. So in your case it would be safe and probably preferable to change aggregator *[]string to aggregator []string and your data will not be copied just a reference passed. Of course along with this change you will need to change all code that was previously dereferencing aggregator e.g.

// Change this line
*aggregator = append(*aggregator, path)
// To this
aggregator = append(aggregator, path)

The reasoning for doing this would likely stem from C based languages where arrays are simply pointer to the start of allocated memory.

Note: All other types including structs do not follow this pattern (interfaces are kind of another exception interesting read). Also this code looks like it could be greatly simplified with filepath.Walk().

For a bit more information although more targeted at maps see this blog post.