I need to access a data structure in 2 different ways:
- Either in a classical way, using the data types defined in the structure
- Either by using the underlying bitmap, for reading and writing bits
What’s the idiomatic way to do that in Crystal ?
I need to access a data structure in 2 different ways:
What’s the idiomatic way to do that in Crystal ?
You can use Crystal unions, which are type-safe.
Then you can also use C unions: just declare them under a lib: https://crystal-lang.org/reference/syntax_and_semantics/c_bindings/union.html
The real question is: why do you need C-like unions? What are you trying to do?
I need it for the development of a genetic algorithm, where a chromosome is defined by a data structure comprising different elementary types, but is subject to mutations on 1 or more random bits throughout the data structure.
I see. I guess without seeing the actual data type there’s not much more help I can give.
Here is a basic example to illustrate my need.
struct Chromo
property gen1, gen2
def initialize(@gen1 : UInt8, @gen2 : UInt16)
end
def bits
pointerof(@gen1)
end
end
end
ch = Chromo.new(33, 66)
I suppose that the 2 integers are quite contiguous in memory
| gen1 : UInt8 | gen2 : UInt16 |
| | |
+-----------------------------------------------+
|0 1 2 |
|0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9|0|1|2|3|
+-----------------------------------------------+
| | |
What I’d like to be able to do is reading or writing any of the 24 bits of the 2 instance variables, something like
x = ch.bits[0..3]
ch.bits[7] = 1
This assumes that the pointer can be assigned the BitArray(24) type.
Is this possible and if yes, what syntax for ?
In fact, when I think about it, both types of data are much larger than necessary.
gen1
would be satisfied with 3 bits and gen2
with 5, all of which would fit into a UInt8
. The bitfields of C allow this.
Is there some sort of workaround for Crystal ?
Thank you for enlightening me on these questions
I think that to make sure things are contiguous in memory you need to put a @[Packed]
annotation on top of the struct, otherwise fields will be aligned according to the platform. Check https://crystal-lang.org/reference/syntax_and_semantics/annotations/built_in_annotations.html
But then my advice would be to represent the struct as those bits, and provide accessors to get the data. Something like:
@[Packed]
struct Chromo
@data : StaticArray(Bool, 24)
def initialize(gen1 : UInt8, gen2 : UInt16)
@data = StaticArray(Bool, 24).new(false)
@data.to_unsafe.as(UInt8*).value = gen1
(@data.to_unsafe.as(UInt8*) + 1).as(UInt16*).value = gen2
end
def gen1
@data.to_unsafe.as(UInt8*).value
end
def gen2
(@data.to_unsafe.as(UInt8*) + 1).as(UInt16*).value
end
end
chromo = Chromo.new(1, 2)
p chromo
p chromo.gen1
p chromo.gen2
But then returning pointerof
from a method is extremely unsafe, I wouldn’t recommend doing that, though maybe there’s no other way. It really depends on what interface you want to have for your type. From your example it looks like you can manipulate bits arbitrarily, so I don’t know.
@Asterite Hi, thanks for your advice.
Your sample code does not suit my needs however, because altering @data
does not impact the variables gen1 and gen2, and vice versa (due to the fact that StaticArray is an array of bytes, not bits , I suppose).
I tried to use BitArray, but to no avail! So, I think I’ll just work with strings of pseudo-bits 0 and 1 for now !
Thanks anyway.
So, I think StaticArray of Bool doesn’t work well. Well, it does, it’s just that getting/setting the bit of every position doesn’t seem to show the correct result.
You can use a StaticArray of UInt8 though:
struct Chromo
@data : StaticArray(UInt8, 3)
def initialize(gen1 : UInt8, gen2 : UInt16)
@data = StaticArray(UInt8, 3).new(0)
@data.to_unsafe.as(UInt8*).value = gen1
(@data.to_unsafe.as(UInt8*) + 1).as(UInt16*).value = gen2
end
def gen1
@data.to_unsafe.as(UInt8*).value
end
def gen2
(@data.to_unsafe.as(UInt8*) + 1).as(UInt16*).value
end
def change!
@data[0] = 10
@data[1] = 20
@data[2] = 30
end
end
chromo = Chromo.new(1, 20)
puts "before"
p chromo.gen1
p chromo.gen2
chromo.change!
puts "after"
p chromo.gen1
p chromo.gen2
Thanks @Asterite, it works, now.
I note this possibility, even if it doesn’t suit me for the moment, because I lose direct access to read/write bits!