Why Slice(T).new(cap) only support for primitive type?

  • Slice(T).new(cap)
 88 | {% raise "Can only use primitive integers and floats with Slice.new(size), not #{T}" %}
         ^----
Error: Can only use primitive integers and floats with Slice.new(size), not S

but

  • Pointer(T).malloc(cap) is working for any type

The difference between Pointer and Slice is that the latter provides more safety. Attaching a size to a Pointer creates a slice, and that makes it safe to access elements in the slice. So Slice is generally considered “safe”.

When allocating a Slice instance, the allocated items need a valid value. Otherwise they would be uninitialized and code that accesses it could break when the memory layout is invalid.

For primitive number types we can be sure that the uninitialized memory is a valid value (and actually for a few more, so this restriction could theoretically be a bit more lenient).
This might not be the case for other, more complex types. Hence this operation is not allowed in order to ensure memory consistency.
In this case you can use one of the other overloads of Slice.new to provide an explicit initialization for the allocated items.

Or, if you’re sure it’s fine to leave the memory uninitialized, you can allocate via Pointer.malloc and create a slice from that. This extra step should make it more explicit what’s going on and that this might have dangerous consequences.
The allocating variants of Slice.new are just convenience short cuts. But unsafe code should not be convenient.

2 Likes

For the record, the exact list of zero-initializable types, whose all-zero-bytes representation is a valid object, are:

  • All primitive integers and floats, including the other floats in LLVM not available in Crystal;
  • Pointer;
  • Char;
  • Bool;
  • All nilable types, including just Nil;
  • All flag enums, and all non-flag enums containing a zero member;
  • Symbol, if at least 1 symbol is defined (although we don’t really want the ability to implicitly create symbols this way without an actual symbol literal);
  • Void and NoReturn (not meaningful in this context);
  • All StaticArrays, Tuples, and NamedTuples containing only other zero-initializable types;
  • All LLVM vector types (their member type is required to be integer, boolean, float, or pointer);
  • The LLVM x86_mmx and x86_amx register types.

In theory, structs containing only zero-initializable instance variables are also themselves zero-initializable, but they are almost always endowed with domain-specific invariants where the all-zero-bytes representation isn’t semantically valid (e.g. a LibGMP::MPZ with a null limb pointer).

Non-null references are never zero-initializable. Neither is ReferenceStorage, as the type ID cannot be zero for any reference object.

2 Likes

Would be better that It is clearly stated in the documentation that Slice items are non-undefined values ​​compared to Pointers.

Maybe the error could direct to the block veraion of the initializer?