Skip to content

Commit 1e985db

Browse files
committed
RFC: Notation for slicing
1 parent 69a0f68 commit 1e985db

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

active/0000-slice-notation.md

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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

Comments
 (0)