Zig: way to cast arrays to a smaller array

Created on 12 Aug 2018  路  12Comments  路  Source: ziglang/zig

I ran into a wall when trying to implement x25519 without being able to slice-and-dice arrays into smaller arrays. (without @ptrCast)

proposal

Most helpful comment

I went ahead and implemented a variation of @arrayPtr in userspace for the next version of my utils package. It was, as predicted, just a little hairy because of the const/non-const distinction, but I'd already solved that for a different function in utils. I've copied appropriate support functions into this code for convenience.

PS: Forgive the non-standard formatting, as I'm an Allman native.

fn isConst(comptime T: type) bool
{
    comptime
    {
        if(!isPointer(T)) return false;
        const info = @typeInfo(T);
        return info.Pointer.is_const;
    }
}

fn isPointer(comptime T: type) bool
{
    comptime
    {
        const inp_type_id = @typeId(T);
        return inp_type_id == builtin.TypeId.Pointer;
    }
}

fn isSingleItemPointer(comptime T: type) bool
{
    comptime
    {
        if(isPointer(T))
        {
            const info = @typeInfo(T);
            return info.Pointer.size == builtin.TypeInfo.Pointer.Size.One;
        }
        return false;
    }
}

fn isArray(comptime T: type) bool
{
    comptime
    {
        const inp_type_id = @typeId(T);
        return inp_type_id == builtin.TypeId.Array;
    }
}

fn _ArraySliceReturnType(comptime T: type, comptime len: usize) type
{
    debug.assert(isSingleItemPointer(T));
    debug.assert(isArray(T.Child));

    if(isConst(T)) return *const [len]T.Child.Child;
    return *[len]T.Child.Child;
}

pub fn arraySlice(ptr: var, comptime start: usize, comptime len: usize)
    _ArraySliceReturnType(@typeOf(ptr), len)
{
    debug.assert(isSingleItemPointer(@typeOf(ptr)));
    debug.assert(isArray(@typeOf(ptr).Child));

    return @ptrCast(_ArraySliceReturnType(@typeOf(ptr), len), &ptr.*[start]);
}

test "arraySlice"
{
    const array = comptime arr:
    {
        var temp = []u32{0} ** 64;
        for(temp) |*e, i| e.* = @intCast(u32, i);
        break :arr temp;
    };

    const first_half = arraySlice(&array, 0, 32);
    const second_half = arraySlice(&array, 32, 32);

    for(first_half.*) |e, i|  debug.assert(i == e);

    for(second_half.*) |e, i| debug.assert(i + 32 == e);
}

All 12 comments

are you expecting that to make a copy?

yes, but generally I wanted to use it as a function call

fn add(a: [32]const u32, b: [32]const u32) [32]u32 {
...
}

var big: [64]u32 = undefined;
_ = add(big[0..32], big[32..64]);

here is x25519 i was working on http://paste.debian.net/1037660/
stuff like this:

    mainloop(@ptrCast(*[64]u32,work[0..].ptr),e);
    @ptrCast(*[32]u32,work[32..].ptr).* = recip(@ptrCast(*[32]u32,work[32..64].ptr).*);

where having a seperate variable just to pass as an argument is silly.

that example makes sense. seems like we should be able to make *[n]T with some kind of slice syntax. that way we're comptime-documenting the length of the "slice".

as seen in the later example: @ptrCast(*[32]u32,work[32..].ptr).* = recip(@ptrCast(*[32]u32,work[32..64].ptr).*); it should also be able to set a subset of a larger array with a smaller array.

wait. is this issue about copying or referencing?

referencing, but as the function does not modify the array, copying does the same thing in this case.

ok. the OP was misleading then.

// this designates a storage location for 2 u32's
var smaller_array: [2]u32 = ...;

// this parameter does not necessitate any actual storage,
// and will in practice be a const reference, not a copy.
fn foo(a: [2]u32) void {}

// this is definitely a reference, not a copy.
var a: *[2]u32 = ...;

i think the smaller_array example from the OP should not work. perhaps there is something that should work with the a examples in this comment.

here's an idea:

var array: [64]u32 = ...;
var first_half: *[32]u32 = array[0..32];
var second_half: *[32]u32 = array[32..];

In order for this to work, the compiler would need to know the slicing index/bounds at comptime. That's kind of an awkward requirement, since the comptime-knownness of values determines what kinds of type casts are allowed.

More specifically, the compiler really only needs to know the length of the resulting slice, not the start index. That's even more awkward, since you could do array[x..x+32], where x is not comptime known; then neither of the slice values are comptime known, but the length of the slice could be known if you pay sophisticated enough attention to the slicing expressions.

This is leading me to think that maybe this operation should really be a different feature. How about this builtin function:

var array: [64]u32 = ...;
var first_half: *[32]u32 = @arrayPtr(array, 0, 32);
var first_half: *[32]u32 = @arrayPtr(array, 32, 32);

// pseudocode signature:
fn @arrayPtr(array: [n]T, start: usize, comptime len: usize) *[len]T;

I can almost imagine this being implemented in userspace, except for some complications with propagating const qualifiers, and maybe the function should also take a slice in addition to an array.

I think #863 is related

@Hejsil ah, yes. that's what i was going for.

@shawnl i'm still confused why "visa versa" is in the title of this issue. wouldn't "casting" from a smaller array into a larger array only be meaningful with copying?

I went ahead and implemented a variation of @arrayPtr in userspace for the next version of my utils package. It was, as predicted, just a little hairy because of the const/non-const distinction, but I'd already solved that for a different function in utils. I've copied appropriate support functions into this code for convenience.

PS: Forgive the non-standard formatting, as I'm an Allman native.

fn isConst(comptime T: type) bool
{
    comptime
    {
        if(!isPointer(T)) return false;
        const info = @typeInfo(T);
        return info.Pointer.is_const;
    }
}

fn isPointer(comptime T: type) bool
{
    comptime
    {
        const inp_type_id = @typeId(T);
        return inp_type_id == builtin.TypeId.Pointer;
    }
}

fn isSingleItemPointer(comptime T: type) bool
{
    comptime
    {
        if(isPointer(T))
        {
            const info = @typeInfo(T);
            return info.Pointer.size == builtin.TypeInfo.Pointer.Size.One;
        }
        return false;
    }
}

fn isArray(comptime T: type) bool
{
    comptime
    {
        const inp_type_id = @typeId(T);
        return inp_type_id == builtin.TypeId.Array;
    }
}

fn _ArraySliceReturnType(comptime T: type, comptime len: usize) type
{
    debug.assert(isSingleItemPointer(T));
    debug.assert(isArray(T.Child));

    if(isConst(T)) return *const [len]T.Child.Child;
    return *[len]T.Child.Child;
}

pub fn arraySlice(ptr: var, comptime start: usize, comptime len: usize)
    _ArraySliceReturnType(@typeOf(ptr), len)
{
    debug.assert(isSingleItemPointer(@typeOf(ptr)));
    debug.assert(isArray(@typeOf(ptr).Child));

    return @ptrCast(_ArraySliceReturnType(@typeOf(ptr), len), &ptr.*[start]);
}

test "arraySlice"
{
    const array = comptime arr:
    {
        var temp = []u32{0} ** 64;
        for(temp) |*e, i| e.* = @intCast(u32, i);
        break :arr temp;
    };

    const first_half = arraySlice(&array, 0, 32);
    const second_half = arraySlice(&array, 32, 32);

    for(first_half.*) |e, i|  debug.assert(i == e);

    for(second_half.*) |e, i| debug.assert(i + 32 == e);
}

It should be noted that this has long since been included in std.meta.

I'm fairly certain this is a duplicate of the accepted proposal #863

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jorangreef picture jorangreef  路  3Comments

DavidYKay picture DavidYKay  路  3Comments

jorangreef picture jorangreef  路  3Comments

dobkeratops picture dobkeratops  路  3Comments

andrewrk picture andrewrk  路  3Comments