Lisp uses zero origin for lists and vectors. There is no connection between what sequence representation you are using and where you start numbering.
I designed the “string” interface in Quintus Prolog, which had to interoperate with Xerox Lisp. The basis of the whole thing was the relation
ABC = A++B++C & |A|=Drop & |B|= Take & |C|=Rest
which unites substring, concatenation, and searching.
There is no relevance of this here is that Prolog was and remained a consistent 1-origin language. This fundamental string relation does not mention ANY index wgatsoever. All the numeric arguments are COUNTS; lengths of one fragment of the whole or another. This turned out tobe a stunningly effective approach. It virtually eliminated off-by-one errors.
And this is what is happening with string:slice/[2,3]. The second argument is a LENGTH not and index. It should be described as slice(String, Drop, Take) where Drop is the number of characters you do not want and Take is the number of chacters you do want. This has or should have nothing to do with indices.
Turn to APL, where the index origin can be set to 0 or 1 at run time, but TAKE \uparrow DROP \downarrow ARRAY works independently of the index origin.
One reason it is important to understand this parameter as a count, not an index, is that if you used the AWK-style substr(s, index, length) you mightt on occasion, need to pass length(s)+1, which is not the index of anything.
I don’t know why the designers of the string module chose the convention they did, but out of all the conventions I’ve hD to use in the past, this one is arguably the best, and has the merit - unlike its Scheme rival - of not delending on inixes or index origin at all.
Rewrite the documentation to talk about LENGTHS not positions and everything becomes clear.