Now that it's certain the Zig specification (#75) will have a well-defined memory model, it's perfectly legal for userland code to do this:
fn bitCast(comptime Dest: type, value: var) Dest {
return @ptrCast(*Dest, &value).*;
}
One reason perhaps to not do this, is that with copy elision (#287), @bitCast can participate in a no-copy expression, like this:
const Foo = packed struct {
number: u16,
};
const Bar = packed struct {
half1: u8,
half2: u8,
};
export fn entry(n: u16) u8 {
var x = @bitCast(Bar, Foo{
.number = n,
});
return x.half2;
}
With copy-elision, the initialization of x directly initializes a Bar, without a step in between. Contrasted with a userland function:
fn bitCast(comptime Dest: type, value: var) Dest {
return @ptrCast(*Dest, &value).*;
}
export fn entry(n: u16) u8 {
var x = bitCast(Bar, Foo{
.number = n,
});
return x.half2;
}
Now the Foo initialization operates on stack memory which is then passed to the userland bitCast function, and the result is memory-loaded into x.
To achieve the same semantics in userland, one would have to do this:
export fn entry(n: u16) u8 {
var x: Bar = undefined;
@ptrCast(*Foo, &x).* = Foo{
.number = n,
};
return x.half2;
}
While it might be up to taste, I personally like the immediacy and self-explanatory nature of @bitCast - going an extra step via &.* hides the intention, but std.bitCast of course works as well.
I'm slightly concerned however that, if @ptrCast and bit cast are so similar in nature, and there is no more lingual difference (both use @ptrCast), one could easily mismatch the pointer levels with multiple layers of indirection:
fn foo(a: *[*]u32){
var b = @ptrCast([*]i32, a); //either meant *[*]i32, or forgot to dereference a
}
I only now realized that status-quo Zig lets this pass without an error.
Maybe (if the note on copy elision optimization were to be resolved) @ptrCast works as the underlying primitive, and std should have both ptrCast, which explicitly checks that the pointer levels match, and bitCast, which explicitly checks you're not casting between pointers? (That way you have to decide to use std.ptrCast to opt in to checks, or @ptrCast to opt out.)
In that case I would also suggest putting a reference to std.ptrCast and std.bitCast into the documentation of @ptrCast - especially since the language doc is currently the only doc around.
Most helpful comment
While it might be up to taste, I personally like the immediacy and self-explanatory nature of
@bitCast- going an extra step via&.*hides the intention, butstd.bitCastof course works as well.I'm slightly concerned however that, if
@ptrCastand bit cast are so similar in nature, and there is no more lingual difference (both use@ptrCast), one could easily mismatch the pointer levels with multiple layers of indirection:I only now realized that status-quo Zig lets this pass without an error.
Maybe (if the note on copy elision optimization were to be resolved)
@ptrCastworks as the underlying primitive, andstdshould have bothptrCast, which explicitly checks that the pointer levels match, andbitCast, which explicitly checks you're not casting between pointers? (That way you have to decide to usestd.ptrCastto opt in to checks, or@ptrCastto opt out.)In that case I would also suggest putting a reference to
std.ptrCastandstd.bitCastinto the documentation of@ptrCast- especially since the language doc is currently the only doc around.