1

I have a slice that I have guaranteed (in runtime) that its length is at least 8. I want to convert that slice to an array because I need to use std.mem.bytesAsValue() in order to create an f64 from raw bytes (for context, I'm implementing a binary serialization format).

I solved it like this, but I'd like to know if there is a better syntax for achieving the same goal:

    var array: [8]u8 = undefined;
    array[0] = slice[0];
    array[1] = slice[1];
    array[2] = slice[2];
    array[3] = slice[3];
    array[4] = slice[4];
    array[5] = slice[5];
    array[6] = slice[6];
    array[7] = slice[7];
André Staltz
  • 13,304
  • 9
  • 48
  • 58

2 Answers2

3

Given your goal, if alignment works out, you could just cast the slice pointer to a pointer to a f64: @ptrCast(*f64, slice.ptr). This saves you from even having to copy anything, in case you don't need to do that at all.

Note that bytesAsValues is a stdlib function that uses compiler builtins, if the signature doesn't match your needs, you can just skip it and use directly the builtins, like I mentioned above. Don't be afraid to read the stdlib source code.

Here's a godbolt link with some examples on how to do the conversion in-place (by casting the pointer) and by copying everything immediately as a float: https://zig.godbolt.org/z/3Y9o5zvfe

kristoff
  • 456
  • 2
  • 3
  • Thanks for the tips! In my case, I actually have `slice[start..end]` not `slice[0..8]` so I got an error `cast increases pointer alignment`. I suppose the `0..8` informs the compiler that the size is exactly 8, while `start..end` doesn't, even if I have a runtime check that the diff between end and start is 8. – André Staltz Nov 25 '21 at 11:26
  • 1
    This is a solvable problem and it's not related to the size, if you were to pass to `bitCast` a value with the wrong size you would get a different error telling you that in explicit terms. I can't fully explain memory alignment in a SO comment, but tldr: "well-formed" f64 values must have their memory address always divisible by `@sizeOf(f64)` (which is 8). Your error is caused by the fact that the compiler is not sure that you will always increment `start` by 8 (or a multiple) every time. You can use `@alignCast` to make that promise (it's also safety checked by Zig at runtime). – kristoff Nov 25 '21 at 15:00
  • Here's a godbolt that shows this https://zig.godbolt.org/z/zdo3T7a6n – kristoff Nov 25 '21 at 15:01
  • 1
    Almost forgot: if you can't keep that promise (that `start` will always be a multiple of 8), then you can either go back to your copying code or you can tell Zig that you're going to reinterpret the memory in your byte array as a "weirdly-aligned" `f64`, which you can then copy into a normal `f64` variable (by dereferencing the pointer), which basically instructs the compiler to do the copy for you. I was already doing the copy in the second godbolt example. The align cast version is preferable because it might use better instructions depending on the target architecture. – kristoff Nov 25 '21 at 15:20
  • Final godbolt example: https://zig.godbolt.org/z/rcbrezvzv – kristoff Nov 25 '21 at 15:20
1

you can do this by std.mem.copy

var array = slice[0..8].*;

or using std.mem.copy

std.mem.copy(u8, &array, slice[0..2]);

while i think you can just need to put slice[0..8] as argument instead creating a variable

Ali Chraghi
  • 613
  • 6
  • 16
  • Thanks. The first method gave the error `error: attempt to dereference non-pointer type '[]u8'` but the second method worked. – André Staltz Nov 24 '21 at 21:27
  • so you need to remove `.*` – Ali Chraghi Nov 24 '21 at 21:30
  • 1
    @AndréStaltz `slice[0..8].*` should work correctly - `slice` is []u8, `slice[0..8]` is *[8]u8, `slice[0..8].*` should be `[8]u8`. I'm not sure why you're getting that error – pfg Dec 11 '21 at 00:20