If we take a look at the implementation, we can see that’s both arguments are slices. Either way you look at it, you’re setting up a slice.
/// Copy all of source into dest at position 0.
/// dest.len must be >= source.len.
/// If the slices overlap, dest.ptr must be <= src.ptr.
pub fn copyForwards(comptime T: type, dest: []T, source: []const T) void {
for (dest[0..source.len], source) |*d, s| d.* = s;
}
Here’s my test code for reference:
onst mem = @import("std").mem;
const inp: [5]usize = .{ 1, 2, 3, 4, 5 };
var out: [5]usize = undefined;
export fn implicit_slice() void {
mem.copyForwards(usize, &out, &inp);
}
export fn explicit_slice() void {
mem.copyForwards(usize, out[0..], inp[0..]);
}
So here’s what ol’ godbolt has to say about it (no optimization flags and arrays are initialized first)…
implicit_slice:
push rbp
mov rbp, rsp
movabs rdi, offset example.out
mov ecx, 5
movabs rdx, offset example.inp
mov rsi, rcx
call mem.copyForwards__anon_1098
pop rbp
ret
explicit_slice:
push rbp
mov rbp, rsp
movabs rdi, offset example.out
mov ecx, 5
movabs rdx, offset example.inp
mov rsi, rcx
call mem.copyForwards__anon_1098
pop rbp
ret
Identical assembly. Now for something neat… let’s put on -O ReleaseFast
…
explicit_slice:
ret
implicit_slice:
jmp explicit_slice
You can see here that the implicit slice call actually is jumping to explicit slice. It’s decided they’re the same thing when all is said and done.