arrayspointersinstancecrystal-lang

Taking the address of a two dimensional array's element


I been trying to follow the Crystal specification. It shows an example of taking the address of an instance variable. So I have an analogue function, but pointing to an instance of a 2d array

def map_ptr(x : Int32, y : Int32)
  pointerof(@map[x][y])
end

where the @map array is defined somehow similar to:

@map = Array(Array(Room)).new(SIZE) { Array(Room).new(SIZE, Room.new) }
    
(0...SIZE).each do |x|
  (0...SIZE).each do |y|
    room_type = ROOMS.sample
    @map[x][y] = Room.new(room_type)
  end
end

When I try to use the map_ptr function in a assignment like: current_room = map_ptr(0, 0), the Crystal compiler gives me an error that says only:

Error: can't take address of @map[x][y]

Why does it say @map[x][y] and not Room, which is the actual type it should point to? And what am I doing wrong here?


Solution

  • @map[x][y] is a call to Array#[]. You're trying to take the pointer address of its return value. That value is never assigned anywhere, so it doesn't have an address.

    I assume you actually want to get the address of the element x, y inside the @map array. First of all, you should be aware that this is an unsafe operation. Array can reallocate its items, so pointers inside it can be invalidated when adding items. If you don't intent to add items, perhaps it would be better use Slice instead because that has more stability guarantees.

    Both Array and Slice have a #to_unsafe method which returns a pointer to the first item. Then it's just @map.to_unsafe[x].to_unsafe + y to get a pointer inside the second dimension.

    Example with Slice:

    class Room
    end
    SIZE = 5
    
    map = Slice(Slice(Room)).new(SIZE) { Slice(Room).new(SIZE, Room.new) }
        
    map.to_unsafe[3].to_unsafe + 2 # => Pointer(Room)@0x7f80a2b58e80