authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2021-05-06 18:33:21+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2021-05-12 11:24:49+02:00
logcd7b5a37186332e90fee91dd366cba46e3c29ca0
tree547ad7d4a3d3234a429f6993a7ebf52dc50049df
parentc0369575218d3dccb69f62495de31c8e22d41ae9
signaturelock-open Commit is signed but in an unrecognized format.

std/mem: add sliceTo(), deprecate spanZ(), lenZ()

The current spanZ() function will not scan for a 0 terminator if the type is not 0 terminated. This encourages using 0 terminated array types to bind C arrays which hold 0 terminated strings. However, this is a big footgun as nothing in the C type system guarantees there to be a 0 terminator at the end of the array and if there is none this becomes Illegal Behavior in Zig. To solve this, deprecate spanZ() and lenZ(), adding a new sliceTo() function that always scans for the given terminator even if the type is not sentinel terminated.

1 files changed, 188 insertions(+), 0 deletions(-)

lib/std/mem.zig+188
...@@ -602,6 +602,7 @@ test "span" {...@@ -602,6 +602,7 @@ test "span" {
602 try testing.expectEqual(@as(?[:0]u16, null), span(@as(?[*:0]u16, null)));602 try testing.expectEqual(@as(?[:0]u16, null), span(@as(?[*:0]u16, null)));
603}603}
604604
605/// Deprecated: use std.mem.span() or std.mem.sliceTo()
605/// Same as `span`, except when there is both a sentinel and an array606/// Same as `span`, except when there is both a sentinel and an array
606/// length or slice length, scans the memory for the sentinel value607/// length or slice length, scans the memory for the sentinel value
607/// rather than using the length.608/// rather than using the length.
...@@ -630,6 +631,192 @@ test "spanZ" {...@@ -630,6 +631,192 @@ test "spanZ" {
630 try testing.expectEqual(@as(?[:0]u16, null), spanZ(@as(?[*:0]u16, null)));631 try testing.expectEqual(@as(?[:0]u16, null), spanZ(@as(?[*:0]u16, null)));
631}632}
632633
634/// Helper for the return type of sliceTo()
635fn SliceTo(comptime T: type, comptime end: meta.Elem(T)) type {
636 switch (@typeInfo(T)) {
637 .Optional => |optional_info| {
638 return ?SliceTo(optional_info.child, end);
639 },
640 .Pointer => |ptr_info| {
641 var new_ptr_info = ptr_info;
642 new_ptr_info.size = .Slice;
643 switch (ptr_info.size) {
644 .One => switch (@typeInfo(ptr_info.child)) {
645 .Array => |array_info| {
646 new_ptr_info.child = array_info.child;
647 // The return type must only be sentinel terminated if we are guaranteed
648 // to find the value searched for, which is only the case if it matches
649 // the sentinel of the type passed.
650 if (array_info.sentinel) |sentinel| {
651 if (end == sentinel) {
652 new_ptr_info.sentinel = end;
653 } else {
654 new_ptr_info.sentinel = null;
655 }
656 }
657 },
658 else => {},
659 },
660 .Many, .Slice => {
661 // The return type must only be sentinel terminated if we are guaranteed
662 // to find the value searched for, which is only the case if it matches
663 // the sentinel of the type passed.
664 if (ptr_info.sentinel) |sentinel| {
665 if (end == sentinel) {
666 new_ptr_info.sentinel = end;
667 } else {
668 new_ptr_info.sentinel = null;
669 }
670 }
671 },
672 .C => {
673 new_ptr_info.sentinel = end;
674 // C pointers are always allowzero, but we don't want the return type to be.
675 assert(new_ptr_info.is_allowzero);
676 new_ptr_info.is_allowzero = false;
677 },
678 }
679 return @Type(std.builtin.TypeInfo{ .Pointer = new_ptr_info });
680 },
681 else => {},
682 }
683 @compileError("invalid type given to std.mem.sliceTo: " ++ @typeName(T));
684}
685
686/// Takes a pointer to an array, an array, a sentinel-terminated pointer, or a slice and
687/// iterates searching for the first occurrence of `end`, returning the scanned slice.
688/// If `end` is not found, the full length of the array/slice/sentinel terminated pointer is returned.
689/// If the pointer type is sentinel terminated and `end` matches that terminator, the
690/// resulting slice is also sentinel terminated.
691/// Pointer properties such as mutability and alignment are preserved.
692/// C pointers are assumed to be non-null.
693pub fn sliceTo(ptr: anytype, comptime end: meta.Elem(@TypeOf(ptr))) SliceTo(@TypeOf(ptr), end) {
694 if (@typeInfo(@TypeOf(ptr)) == .Optional) {
695 const non_null = ptr orelse return null;
696 return sliceTo(non_null, end);
697 }
698 const Result = SliceTo(@TypeOf(ptr), end);
699 const length = lenSliceTo(ptr, end);
700 if (@typeInfo(Result).Pointer.sentinel) |s| {
701 return ptr[0..length :s];
702 } else {
703 return ptr[0..length];
704 }
705}
706
707test "sliceTo" {
708 try testing.expectEqualSlices(u8, "aoeu", sliceTo("aoeu", 0));
709
710 {
711 var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
712 try testing.expectEqualSlices(u16, &array, sliceTo(&array, 0));
713 try testing.expectEqualSlices(u16, array[0..3], sliceTo(array[0..3], 0));
714 try testing.expectEqualSlices(u16, array[0..2], sliceTo(&array, 3));
715 try testing.expectEqualSlices(u16, array[0..2], sliceTo(array[0..3], 3));
716
717 const sentinel_ptr = @ptrCast([*:5]u16, &array);
718 try testing.expectEqualSlices(u16, array[0..2], sliceTo(sentinel_ptr, 3));
719 try testing.expectEqualSlices(u16, array[0..4], sliceTo(sentinel_ptr, 99));
720
721 const optional_sentinel_ptr = @ptrCast(?[*:5]u16, &array);
722 try testing.expectEqualSlices(u16, array[0..2], sliceTo(optional_sentinel_ptr, 3).?);
723 try testing.expectEqualSlices(u16, array[0..4], sliceTo(optional_sentinel_ptr, 99).?);
724
725 const c_ptr = @as([*c]u16, &array);
726 try testing.expectEqualSlices(u16, array[0..2], sliceTo(c_ptr, 3));
727
728 const slice: []u16 = &array;
729 try testing.expectEqualSlices(u16, array[0..2], sliceTo(slice, 3));
730 try testing.expectEqualSlices(u16, &array, sliceTo(slice, 99));
731
732 const sentinel_slice: [:5]u16 = array[0..4 :5];
733 try testing.expectEqualSlices(u16, array[0..2], sliceTo(sentinel_slice, 3));
734 try testing.expectEqualSlices(u16, array[0..4], sliceTo(sentinel_slice, 99));
735 }
736 {
737 var sentinel_array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
738 try testing.expectEqualSlices(u16, sentinel_array[0..2], sliceTo(&sentinel_array, 3));
739 try testing.expectEqualSlices(u16, &sentinel_array, sliceTo(&sentinel_array, 0));
740 try testing.expectEqualSlices(u16, &sentinel_array, sliceTo(&sentinel_array, 99));
741 }
742
743 try testing.expectEqual(@as(?[]u8, null), sliceTo(@as(?[]u8, null), 0));
744}
745
746/// Private helper for sliceTo(). If you want the length, use sliceTo(foo, x).len
747fn lenSliceTo(ptr: anytype, comptime end: meta.Elem(@TypeOf(ptr))) usize {
748 switch (@typeInfo(@TypeOf(ptr))) {
749 .Pointer => |ptr_info| switch (ptr_info.size) {
750 .One => switch (@typeInfo(ptr_info.child)) {
751 .Array => |array_info| {
752 if (array_info.sentinel) |sentinel| {
753 if (sentinel == end) {
754 return indexOfSentinel(array_info.child, end, ptr);
755 }
756 }
757 return indexOfScalar(array_info.child, ptr, end) orelse array_info.len;
758 },
759 else => {},
760 },
761 .Many => if (ptr_info.sentinel) |sentinel| {
762 // We may be looking for something other than the sentinel,
763 // but iterating past the sentinel would be a bug so we need
764 // to check for both.
765 var i: usize = 0;
766 while (ptr[i] != end and ptr[i] != sentinel) i += 1;
767 return i;
768 },
769 .C => {
770 assert(ptr != null);
771 return indexOfSentinel(ptr_info.child, end, ptr);
772 },
773 .Slice => {
774 if (ptr_info.sentinel) |sentinel| {
775 if (sentinel == end) {
776 return indexOfSentinel(ptr_info.child, sentinel, ptr);
777 }
778 }
779 return indexOfScalar(ptr_info.child, ptr, end) orelse ptr.len;
780 },
781 },
782 else => {},
783 }
784 @compileError("invalid type given to std.mem.sliceTo: " ++ @typeName(@TypeOf(ptr)));
785}
786
787test "lenSliceTo" {
788 try testing.expect(lenSliceTo("aoeu", 0) == 4);
789
790 {
791 var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
792 try testing.expectEqual(@as(usize, 5), lenSliceTo(&array, 0));
793 try testing.expectEqual(@as(usize, 3), lenSliceTo(array[0..3], 0));
794 try testing.expectEqual(@as(usize, 2), lenSliceTo(&array, 3));
795 try testing.expectEqual(@as(usize, 2), lenSliceTo(array[0..3], 3));
796
797 const sentinel_ptr = @ptrCast([*:5]u16, &array);
798 try testing.expectEqual(@as(usize, 2), lenSliceTo(sentinel_ptr, 3));
799 try testing.expectEqual(@as(usize, 4), lenSliceTo(sentinel_ptr, 99));
800
801 const c_ptr = @as([*c]u16, &array);
802 try testing.expectEqual(@as(usize, 2), lenSliceTo(c_ptr, 3));
803
804 const slice: []u16 = &array;
805 try testing.expectEqual(@as(usize, 2), lenSliceTo(slice, 3));
806 try testing.expectEqual(@as(usize, 5), lenSliceTo(slice, 99));
807
808 const sentinel_slice: [:5]u16 = array[0..4 :5];
809 try testing.expectEqual(@as(usize, 2), lenSliceTo(sentinel_slice, 3));
810 try testing.expectEqual(@as(usize, 4), lenSliceTo(sentinel_slice, 99));
811 }
812 {
813 var sentinel_array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
814 try testing.expectEqual(@as(usize, 2), lenSliceTo(&sentinel_array, 3));
815 try testing.expectEqual(@as(usize, 5), lenSliceTo(&sentinel_array, 0));
816 try testing.expectEqual(@as(usize, 5), lenSliceTo(&sentinel_array, 99));
817 }
818}
819
633/// Takes a pointer to an array, an array, a vector, a sentinel-terminated pointer,820/// Takes a pointer to an array, an array, a vector, a sentinel-terminated pointer,
634/// a slice or a tuple, and returns the length.821/// a slice or a tuple, and returns the length.
635/// In the case of a sentinel-terminated array, it uses the array length.822/// In the case of a sentinel-terminated array, it uses the array length.
...@@ -688,6 +875,7 @@ test "len" {...@@ -688,6 +875,7 @@ test "len" {
688 }875 }
689}876}
690877
878/// Deprecated: use std.mem.len() or std.mem.sliceTo().len
691/// Takes a pointer to an array, an array, a sentinel-terminated pointer,879/// Takes a pointer to an array, an array, a sentinel-terminated pointer,
692/// or a slice, and returns the length.880/// or a slice, and returns the length.
693/// In the case of a sentinel-terminated array, it scans the array881/// In the case of a sentinel-terminated array, it scans the array