Golang cannot range over pointer to slice

go
48,851

Solution 1

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.

Solution 2

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

Solution 3

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)
    }
}

Solution 4

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.

Share:
48,851
Lee
Author by

Lee

Updated on May 18, 2020

Comments

  • Lee
    Lee over 2 years

    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
        }
    
  • Lee
    Lee over 8 years
    No, it doesn't have to be a pointer I am just trying to get it to work.
  • Lee
    Lee over 8 years
    Can you show me how to do it without the pointers? I copied the key part to play.golang.org/p/KonrOk3bp-
  • Lee
    Lee over 8 years
    There is a problem with line 19.
  • Lee
    Lee over 8 years
    I thought that if you didn't pass a pointer to the array when calling populateClassRelationships then you are just populating a copy and the original will be blank on return in ClassesForLastNDays
  • Lee
    Lee over 8 years
    Now getting cannot use &classes (type **[]entities.Class) as type []entities.Class in function argument
  • fixermark
    fixermark almost 7 years
    Clarification: There is one use for pointer to a slice: If multiple sections of the program need to share the same slice, so modifications to the slice itself are reflected in other sections of the program (for instance, if removing an element from a slice by doing a = append(a[:i], a[i+1:]...) should be reflected in slices held by other data structures). This is rarely what you want, however, and is not thread-safe without locking.
  • fIwJlxSzApHEZIl
    fIwJlxSzApHEZIl almost 6 years
    Just spent hours debugging as I was trying to be too clever. I had a function that was returning a []*structs as I didn't want to duplicate the memory. This wreaked havoc on my code in a for each loop when building the return result as every pointer in my slice pointed to the memory address of the for each loop iterator which means that the slice that I ended up returning had 10 pointers all pointing to the same struct. When I took pointers out of the equation and just returned a normal slice after my for each loop everything worked perfectly fine.
  • domoarigato
    domoarigato almost 5 years
    @HassaanSalik - thanks for the upvote, so I'll pass along that I don't ever do this anymore. A slice is already a pointer type, so there usually shouldn't ever be a reason to need a *slice - I've since refactored this code, and I'd recommend you look for opportunities to do the same.