Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang cannot range over pointer to slice

Tags:

go

I keep getting this error when trying to range over a slice pointer.

app/domain/repositories/class_repository.go:24: cannot range over classes (type *[]entities.Class)

What am I doing wrong?

Here is the struct:

 package repositories

import (
    "mobifit/app/domain/entities"
)

type ClassRepository struct {
    *Repository
}

func (c *ClassRepository) ClassesForLastNDays(days int) *[]entities.Class {
    classes := new([]entities.Class)
    query := Select("*").
        From("Class").
        Where("VisibleAt > CURRENT_TIMESTAMP() - INTERVAL ? DAY").
        OrderBy("ClassTypeId").
        Sql()
    c.Repository.Select(classes, query, days)
    c.populateClassRelationships(classes)
    return classes
}

func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for i := range classes {  <<<<<<<<<<< Here is the problem
        class := classes[i]

        // ClassType
        c.Repository.GetById(class.ClassType, class.ClassTypeId)

        //Instructor
        c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

        // Equipment
        query := Select("E.*").
            From("Equipment E").
            Join("ClassEquipment CE on E.Id = CE.EquipmentId").
            Where("CE.ClassId = ?").
            Sql()
        c.Repository.Select(class.Equipment, query, class.Id)
    }
}

Here is the Class struct:

package entities

import (
    "time"
)

    type Class struct {
        Id                int
        ClassTypeId       int
        VideoPath         string
        VideoSize         int
        Duration          float64
        CreatedAt         time.Time
        VisibleAt         time.Time
        NoLongerVisibleAt time.Time

        // Relationships
        ClassType  ClassType
        Instructor User
        Equipment  []Equipment
    }
like image 730
Lee Avatar asked Jan 22 '14 08:01

Lee


4 Answers

You're assuming the pointer to a slice will be automatically dereferenced for the iteration.

That's not the case and there's no reason for that because a slice is already a kind of pointer, rendering a pointer to a slice totally useless.

From Effective Go :

If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array.

Internally, a slice is made of

  • a pointer to the first element of the slice in the underlying array
  • the length of the slice
  • the capacity of the slice (the slice can usually be extended until the end of the array)

This structure is very small, rendering a pointer useless.

like image 98
Denys Séguret Avatar answered Nov 14 '22 06:11

Denys Séguret


From Effective Go:

If you're looping over an array, slice, string, or map, or reading from a channel, a range clause can manage the loop.

You are attempting to iterate over a pointer to a slice which is a single value, not a collection therefore is not possible.

Change the argument to populateClassRelationships to be an slice, not a pointer to a slice. Or you could dereference the pointer:

func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for i := range *classes { // dereferencing the pointer to get the actual slice
        class := classes[i]

        // ClassType
        c.Repository.GetById(class.ClassType, class.ClassTypeId)

        //Instructor
        c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

        // Equipment
        query := Select("E.*").
            From("Equipment E").
            Join("ClassEquipment CE on E.Id = CE.EquipmentId").
            Where("CE.ClassId = ?").
            Sql()
        c.Repository.Select(class.Equipment, query, class.Id)
    }
}
like image 30
Agis Avatar answered Nov 14 '22 04:11

Agis


if you need to pull an individual element from the *slice, you have to dereference it first like this: (*slice)[0]. I pounded my head against *slice[0] for about 6 hours before I realized this. It has to do with the order of operations, and is not, IMO, a very elegant result.

I ended up writing some pointer receiver methods to do in-place modifications like append and pop in a more, to my mind, reasonable way - an example can be found here: https://play.golang.org/p/qZEYMcPHl4

like image 13
domoarigato Avatar answered Nov 14 '22 04:11

domoarigato


You could dereference the pointer:

func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for _, class := range *classes { // NOTE the * dereference
    // ClassType
    c.Repository.GetById(class.ClassType, class.ClassTypeId)

    //Instructor
    c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

    // Equipment
    query := Select("E.*").
        From("Equipment E").
        Join("ClassEquipment CE on E.Id = CE.EquipmentId").
        Where("CE.ClassId = ?").
        Sql()
    c.Repository.Select(class.Equipment, query, class.Id)
    }
}

I also changed the range clause as I don't think you're modifying classes.

like image 5
Martin Gallagher Avatar answered Nov 14 '22 05:11

Martin Gallagher