Slice and index return type

I know the topic has been addressed before, but since the subject has been closed I'm writing here.

Is there any reason why slice and index doesn't have optional or result as return type?

I know this might give a little overhead where it can't be optimized out, but since rust allow destructive pattern matching it could defiantly improve the readability and case analysis of control flow.

as an example assume we are processing some slice arr of u8, we which test against that the second element from start position are of value x.

as of now this would be

if arr.len() >= 3 {
  if arr[1] == x {
     // do case 1
     }  else {
         // do case 2
     }
} else { 
   // do case 2
}

if it returned a result type it would look like this

if let Ok([a,x,b]) = arr[0..3] { // where a and b are casting values, and x are the pattern from before
   // do case 1 with a and b
} else {
   // do case 2
}

I use the function

pub const fn try_slice<T>(content: &[T], from: usize, to: usize) -> Option<&[T]> {
        if content.len() >= to && from <= to {
            Some(&content[from..to])
        } else { 
            // this will most likely be optimized away by the compiler
            // since this is only to catch none accepted patterns
            None
        }
    }

where the None case most likely would be optimized away, and generate better performing code because of less branching.

we could still pattern match the error cases if needed, but they could be contrained to an easy matchable Error set, instead of just panicking. if we want the current behavior it is just to unwrap the return value with the operator ? and it will still panic. when it comes to implementation in the compiler it should not impose any new problems with current pointer and boundary optimizations.

It would most defiantly make programmers remember index checking when slicing and indexing since it then will be moved into a place where the intellisense could catch it. it will also make library miss use harder and the API better, since you as an implementer could constraint the possible output to a finite set.

Coming from SML and F# where you can do destructive case matching on size directly like

match arr {
 [a] => // do something with a,
 [a,b] => // do something else,
 .....
}

This destructive patterns make sense. I know that the last one most likely would not conform to 'the zero cost abstraction' and I can live with not having this.

Hi, welcome to the Rust Internals forum.

You should format your code blocks using three backticks like

```
if example { 42 } else { /* details omitted */ }
```

resulting in a monospace codeblock with syntax highlighting like this:

if example { 42 } else { /* details omitted */ }
1 Like

thx

Well, you can do something like this

fn foo<T: Eq>(arr: &[T], x: &T) {
    match arr {
        [a, b, c, ..] if b == x => {
            // case 1
        }
        [a, b, c, ..] => {
            // case 2
        }
        _ => {
            // case 3
        }
    }
}

(this example in the Rust playground)


No need to define this yourself, the .get(...) method on slices does support ranges:

pub fn try_slice<T>(content: &[T], from: usize, to: usize) -> Option<&[T]> {
    content.get(from..to)
}
3 Likes

As @steffahn already mentioned, those fallible (Option-returning) operations exist, they are called get and get_mut.

Also, may I ask for this thread to be moved to a different category? It has nothing to do with Unsafe Code Guidelines.

1 Like

I think it would nonetheless be useful if you link the subject you are referring to somewhere.

I think the main reason is to stay somewhat similar to the behavior of the index operator in languages such as C/C++. Of course while avoiding the undefined behavior on out-of-bounds indexing.

In particular it’s not really about overhead

this is inaccurate, both a panicking index operation like the current arr[ix] expression behavior and something returning a Option need to do a bounds check. Assuming some common compiler optimizations take place, like inlining, the alternatives &arr[ix] vs arr.get(ix).unwrap() should be performing the same. The only overhead would be syntactically. Also a Option-valued index operation couldn’t be a “place expression” anymore:

Note how the equivalent to arr.get(ix).unwrap(), that is &arr[ix] needs the extra &. Actually, arr[ix] on its own in most cases doesn’t even compile (except when the type inside of the slice is copyable, e.g. an integer). Similarly &mut arr[ix] corresponds to arr.get_mut(ix).unwrap().

This is wrong. The ? operator does not panic but instead introduces an early return from the containing function/closure.


And last but not least a general remark: Rust’s indexing operator is stable (and used a lot). This means that it isn’t going to change due to Rust’s stability guarantees.


Edit: I just want to add that I do agree that using index operators in Rust is an easy way to introduce unwanted panics into your code. If you want to be extra sure and avoid any potentially panicking use of index operators, you can e.g. use this restriction lint from clippy. For a less extreme solution, one should just always keep in mind that foo[bar] can panic and that one should probably prefer foo.get(bar)/foo.get_mut(bar) in many cases.

2 Likes