Currently, most of the crypto algorithms in the standard library use slices for input/output parameters, even in cases where the input/output has a fixed size. Is there any reason for using a slice, or would a pointer to a fixed-size array be more appropriate?
For example, AES.encrypt has a signature pub fn encrypt(ctx: Self, dst: []u8, src: []const u8) void, but dst and src both refer to a single 16-byte block, so using pub fn encrypt(ctx: Self, dst: *[16]u8, src: *[16]const u8) void makes more sense.
Originally when the interfaces were created the fixed-width array types as input/output parameters were quite awkward to use with slices it required explicit casts to the specific length if taking from a slice. A lot of the parameters do have an internal assert already so the intention was to move when the ergonomics were reasonable.
I haven't looked for a while and may be mistaken, but I think this only requires a dereference to coerce to a fixed array now. Regardless, those function signatures are preferable and I agree with your thoughts on the api.
What is preferred in terms of pass-by-value vs. pass-by-const-pointer? ChaCha20 and Gimli implementations currently pass arrays by value (where possible), while all others use pointers.
What is preferred in terms of pass-by-value vs. pass-by-const-pointer?
pass-by-value.
pass-by-value is allowed to be optimized to pass-by-const-pointer; but not vice-versa.
Likewise, should we return arrays directly, instead of taking a mutable pointer as a parameter?
The situation complicates a bit with different hash functions. All of them now use a signature pub fn final(d: *Self, out: []u8) void, but have two different behaviors:
digest_length bytes and write exactly digest_length bytes to the out parameter. In that case, the signature could be changed to pub fn final(d: *Self) [digest_length]u8.out parameter. They also specify digest_length, but that is only a suggestion.How should the API look in the second case? We could either:
digest_length-sized output.pub fn final(d: *Self) [digest_length]u8, which returns an output of default size. The current method would then be renamed to something like pub fn finalVariable(d: *Self, out: []u8) void (with existing behavior).@zigazeljko I think for output parameters we should take a slice to write to; both because copying an array into a slice is (currently) unergonomic, and for consistency with variable-length-output hashes.
However, I think fixed-width outputs (like MD5, SHA1, etc) can have the signature: pub fn final(d: *Self, out: *[digest_length]) void due to slicing by a comptime-known length decaying to a pointer-to-array.
copying an array into a slice is (currently) unergonomic
How so? I think the current syntax is not that bad:
out[0..32].* = Sha256.hash(data);
If you are just passing the hash around, you would use arrays (and pointers to arrays) directly, so that would not be an issue. And I think the extra verbosity (the [0..32] part) when writing to a slice is beneficial, since it makes it clear how many bytes were written.
@tiehuis What do you think?
Most helpful comment
Originally when the interfaces were created the fixed-width array types as input/output parameters were quite awkward to use with slices it required explicit casts to the specific length if taking from a slice. A lot of the parameters do have an internal assert already so the intention was to move when the ergonomics were reasonable.
I haven't looked for a while and may be mistaken, but I think this only requires a dereference to coerce to a fixed array now. Regardless, those function signatures are preferable and I agree with your thoughts on the api.