Pointers are still a mystery to me

Your examples are without use-case context so they aren’t the right example of showing how pointer is used. Hence, the confusion.

For simple function context as shown above, example here:

func HelloWorld() {
	i, j := 42, 2701

	fmt.Println(i) // read i directly
	i = 21         // set new i value directly
	fmt.Println(i)  // see the new value of i


	j = j / 37   // divide j directly
	fmt.Println(j) // see the new value of j
}

Then the 2nd one makes a lot more sense as it makes no sense to use pointers at all.

In the context of the following processing function however:

func Calculate(i, j *int) {
    if i == nil || j == nil {
         return  // handle error and return. Can't operate
    }

	fmt.Println(*i) // read i through the pointer
	*i = 21         // set value directly to the memory i
	fmt.Println(*i)  // see the new value of i

	*j = *j / 37   // divide j through the pointer
	fmt.Println(*j) // set value directly to the memory j
}

func main() {
       i := 42;
       j := 2701;
       Calculate(&i, &j);

      fmt.Printf("After caculate i is: %v\n", i); // you won't get 42
      fmt.Printf("After caculate j is: %v\n", j); // you won't get 2701
}

Then it makes sense to use pointer. Note that you do not need to assign p pointer just to use them but you have to check the pointer’s viability (they can be supplied with nil symbolizing no data in memory is given).

The introduction and its mechanics was explained in this forum before here:

Generally speaking, you use pointer in the event where you want to:

  1. get/set a set of data stored in a specific memory rather than copy-pasting all over the places (e.g. function taking non-pointer type are usually copied as duplicate and not directly modifying the original one).
  2. Separate the processing function away from actual runtime data.
  3. Compositing a larger data structure.

A good case: if you’re working on a function for a complex data structure, as below, pointer is highly essential as duplicating can be quite daunting:

type Wheel struct {
      Clean bool
      Dimension int64
      Brand string
      ...
}

// create more component data structure, like door, windows, engine, etc. 

type Car struct {
      WheelA *Wheel
      WheelB *Wheel
      WheelC *Wheel
      WheelD *Wheel
      ...   
}

func WashTheCar(car *Car) {
        if car != nil {
                WashTheWheel(car.WheelA);
                WashTheWheel(car.WheelB);
                WashTheWheel(car.WheelC);
                WashTheWheel(car.WheelD);
                // wash something else ... like door, windows, wipers, bumper, car plate, etc.
        }
}

func WashTheWheel(wheel *Wheel) {
       if wheel != nil {
              wheel.Clean = true
       }
}

func main() {
        mercedes := &Car {
               // fill in car data
        };

        lambo := &Car {
               // fill in car data
        };

        WashTheCar(mercedes);
        WashTheCar(lambo);
}

You can still re-write the above without pointer implementations using return value but for a large data structure, you will find at one point you will be bottle-necking yourself copy-pasting the return-ed value here and there, making your main function unnecessary long and complicated. Also, FYI, at low-level, these copy-pasting can cost a lot of compute cycles just to achieve the intended effect: directly update the already memory allocated mercedes and lambo data for a given function WashTheCar.

5 Likes