this has to do with the fact that slice returns an array, relevant source documentation from Array#slice:
* call-seq:
* array[index] -> obj or nil
* array[start, length] -> an_array or nil
* array[range] -> an_array or nil
* array.slice(index) -> obj or nil
* array.slice(start, length) -> an_array or nil
* array.slice(range) -> an_array or nil
which suggests to me that if you give the start that is out of bounds, it will return nil, thus in your example array[4,0]
asks for the 4th element that exists, but asks to return an array of zero elements. While array[5,0]
asks for an index out of bounds so it returns nil. This perhaps makes more sense if you remember that the slice method is returning a new array, not altering the original data structure.
EDIT:
After reviewing the comments I decided to edit this answer. Slice calls the following code snippet when the arg value is two:
if (argc == 2) {
if (SYMBOL_P(argv[0])) {
rb_raise(rb_eTypeError, "Symbol as array index");
}
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += RARRAY(ary)->len;
}
return rb_ary_subseq(ary, beg, len);
}
if you look in the array.c
class where the rb_ary_subseq
method is defined, you see that it is returning nil if the length is out of bounds, not the index:
if (beg > RARRAY_LEN(ary)) return Qnil;
In this case this is what is happening when 4 is passed in, it checks that there are 4 elements and thus does not trigger the nil return. It then goes on and returns an empty array if the second arg is set to zero. while if 5 is passed in, there are not 5 elements in the array, so it returns nil before the zero arg is evaluated. code here at line 944.
I believe this to be a bug, or at least unpredictable and not the 'Principle of Least Surprise'. When I get a few minutes I will a least submit a failing test patch to ruby core.