|
| 1 | +- Start Date: (fill me in with today's date, 2014-08-12) |
| 2 | +- RFC PR #: (leave this empty) |
| 3 | +- Rust Issue #: (leave this empty) |
| 4 | + |
| 5 | +# Summary |
| 6 | + |
| 7 | +This RFC adds *overloaded slice notation*: |
| 8 | + |
| 9 | +- `foo[]` for `foo.as_slice()` |
| 10 | +- `foo[n..m]` for `foo.slice(n, m)` |
| 11 | +- `foo[n..]` for `foo.slice_from(n)` |
| 12 | +- `foo[..m]` for `foo.slice_to(m)` |
| 13 | +- `mut` variants of all the above |
| 14 | + |
| 15 | +via two new traits, `Slice` and `SliceMut`. |
| 16 | + |
| 17 | +# Motivation |
| 18 | + |
| 19 | +There are two primary motivations for introducing this feature. |
| 20 | + |
| 21 | +### Ergonomics |
| 22 | + |
| 23 | +Slicing operations, especially `as_slice`, are a very common and basic thing to |
| 24 | +do with vectors, and potentially many other kinds of containers. We already |
| 25 | +have notation for indexing via the `Index` trait, and this RFC is essentially a |
| 26 | +continuation of that effort. |
| 27 | + |
| 28 | +The `as_slice` operator is particularly important. Since we've moved away from |
| 29 | +auto-slicing in coercions, explicit `as_slice` calls have become extremely |
| 30 | +common, and are one of the |
| 31 | +[leading ergonomic/first impression](https://github.com/rust-lang/rust/issues/14983) |
| 32 | +problems with the language. There are a few other approaches to address this |
| 33 | +particular problem, but these alternatives have downsides that are discussed |
| 34 | +below (see "Alternatives"). |
| 35 | + |
| 36 | +### Error handling conventions |
| 37 | + |
| 38 | +We are gradually moving toward a Python-like world where notation like `foo[n]` |
| 39 | +calls `fail!` when `n` is out of bounds, while corresponding methods like `get` |
| 40 | +return `Option` values rather than failing. By providing similar notation for |
| 41 | +slicing, we open the door to following the same convention throughout |
| 42 | +vector-like APIs. |
| 43 | + |
| 44 | +# Detailed design |
| 45 | + |
| 46 | +The design is a straightforward continuation of the `Index` trait design. We |
| 47 | +introduce two new traits, for immutable and mutable slicing: |
| 48 | + |
| 49 | +```rust |
| 50 | +trait Slice<Idx, S> { |
| 51 | + fn as_slice<'a>(&'a self) -> &'a S; |
| 52 | + fn slice_from(&'a self, from: Idx) -> &'a S; |
| 53 | + fn slice_to(&'a self, to: Idx) -> &'a S; |
| 54 | + fn slice(&'a self, from: Idx, to: Idx) -> &'a S; |
| 55 | +} |
| 56 | + |
| 57 | +trait SliceMut<Idx, S> { |
| 58 | + fn as_mut_slice<'a>(&'a mut self) -> &'a mut S; |
| 59 | + fn slice_from_mut(&'a mut self, from: Idx) -> &'a mut S; |
| 60 | + fn slice_to_mut(&'a mut self, to: Idx) -> &'a mut S; |
| 61 | + fn slice_mut(&'a mut self, from: Idx, to: Idx) -> &'a mut S; |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +(Note, the mutable names here are part of likely changes to naming conventions |
| 66 | +that will be described in a separate RFC). |
| 67 | + |
| 68 | +These traits will be used when interpreting the following notation: |
| 69 | + |
| 70 | +*Immutable slicing* |
| 71 | + |
| 72 | +- `foo[]` for `foo.as_slice()` |
| 73 | +- `foo[n..m]` for `foo.slice(n, m)` |
| 74 | +- `foo[n..]` for `foo.slice_from(n)` |
| 75 | +- `foo[..m]` for `foo.slice_to(m)` |
| 76 | + |
| 77 | +*Mutable slicing* |
| 78 | + |
| 79 | +- `foo[mut]` for `foo.as_mut_slice()` |
| 80 | +- `foo[mut n..m]` for `foo.slice_mut(n, m)` |
| 81 | +- `foo[mut n..]` for `foo.slice_from_mut(n)` |
| 82 | +- `foo[mut ..m]` for `foo.slice_to_mut(m)` |
| 83 | + |
| 84 | +Like `Index`, uses of this notation will auto-deref just as if they were method |
| 85 | +invocations. So if `T` implements `Slice<uint, [U]>`, and `s: Smaht<T>`, then |
| 86 | +`s[]` compiles and has type `&[U]`. |
| 87 | + |
| 88 | +# Drawbacks |
| 89 | + |
| 90 | +The main drawback is the increase in complexity of the language syntax. This |
| 91 | +seems minor, especially since the notation here is essentially "finishing" what |
| 92 | +was started with the `Index` trait. |
| 93 | + |
| 94 | +# Alternatives |
| 95 | + |
| 96 | +For improving the ergonomics of `as_slice`, there are two main alternatives. |
| 97 | + |
| 98 | +## Coercions: auto-slicing |
| 99 | + |
| 100 | +One possibility would be re-introducing some kind of coercion that automatically |
| 101 | +slices. |
| 102 | +We used to have a coercion from (in today's terms) `Vec<T>` to |
| 103 | +`&[T]`. Since we no longer coerce owned to borrowed values, we'd probably want a |
| 104 | +coercion `&Vec<T>` to `&[T]` now: |
| 105 | + |
| 106 | +```rust |
| 107 | +fn use_slice(t: &[u8]) { ... } |
| 108 | + |
| 109 | +let v = vec!(0u8, 1, 2); |
| 110 | +use_slice(&v) // automatically coerce here |
| 111 | +use_slice(v.as_slice()) // equivalent |
| 112 | +``` |
| 113 | + |
| 114 | +Unfortunately, adding such a coercion requires choosing between the following: |
| 115 | + |
| 116 | +* Tie the coercion to `Vec` and `String`. This would reintroduce special |
| 117 | + treatment of these otherwise purely library types, and would mean that other |
| 118 | + library types that support slicing would not benefit (defeating some of the |
| 119 | + purpose of DST). |
| 120 | + |
| 121 | +* Make the coercion extensible, via a trait. This is opening pandora's box, |
| 122 | + however: the mechanism could likely be (ab)used to run arbitrary code during |
| 123 | + coercion, so that any invocation `foo(a, b, c)` might involve running code to |
| 124 | + pre-process each of the arguments. While we may eventually want such |
| 125 | + user-extensible coercions, it is a *big* step to take with a lot of potential |
| 126 | + downside when reasoning about code, so we should pursue more conservative |
| 127 | + solutions first. |
| 128 | + |
| 129 | +## Deref |
| 130 | + |
| 131 | +Another possibility would be to make `String` implement `Deref<str>` and |
| 132 | +`Vec<T>` implement `Deref<[T]>`, once DST lands. Doing so would allow explicit |
| 133 | +coercions like: |
| 134 | + |
| 135 | +```rust |
| 136 | +fn use_slice(t: &[u8]) { ... } |
| 137 | + |
| 138 | +let v = vec!(0u8, 1, 2); |
| 139 | +use_slice(&*v) // take advantage of deref |
| 140 | +use_slice(v.as_slice()) // equivalent |
| 141 | +``` |
| 142 | + |
| 143 | +There are at least two downsides to doing so, however: |
| 144 | + |
| 145 | +* It is not clear how the method resolution rules will ultimately interact with |
| 146 | + `Deref`. In particular, a leading proposal is that for a smart pointer `s: Smaht<T>` |
| 147 | + when you invoke `s.m(...)` only *inherent* methods `m` are considered for |
| 148 | + `Smaht<T>`; *trait* methods are only considered for the maximally-derefed |
| 149 | + value `*s`. |
| 150 | + |
| 151 | + With such a resolution strategy, implementing `Deref` for `Vec` would make it |
| 152 | + impossible to use trait methods on the `Vec` type except through UFCS, |
| 153 | + severely limiting the ability of programmers to usefully implement new traits |
| 154 | + for `Vec`. |
| 155 | + |
| 156 | +* The idea of `Vec` as a smart pointer around a slice, and the use of `&*v` as |
| 157 | + above, is somewhat counterintuitive, especially for such a basic type. |
| 158 | + |
| 159 | +Ultimately, notation for slicing seems desireable on its own merits anyway, and |
| 160 | +if it can eliminate the need to implement `Deref` for `Vec` and `String`, all |
| 161 | +the better. |
0 commit comments