-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: overloaded slice notation #198
Conversation
I like this. +1 |
It occurs to me that other languages tend to use |
Super 👍 here. I'm not sure I'm aware of precedent either way for Also, in Ruby, |
I think I agree that I think inclusive/exclusive is probably overkill here. |
Python uses |
+1. Would love to see this! |
|
||
```rust | ||
trait Slice<Idx, S> { | ||
fn as_slice<'a>(&'a self) -> &'a S; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, returning a reference like this limits the trait to contiguous-memory slices, e.g. it wouldn't be possible to write x[n..m]
on a RingBuf
, or rope, or a hypothetical StridedSlice<'a, T> { data: &'a [T], step: uint }
type that represents every step
th element of data
(i.e. strided[i]
== data[i * step]
).
I think this may mean it is only possible to use with [T]
/str
and Vec<T>
/String
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to use this with other unsized types as well (i.e., structs with embedded [T]
fields).
There are two ways to get the kind of generality you have in mind:
- HKT. In that case, the type "S" here could instead be a lifetime-indexed family of types, so you could use
StridedSlice
for example. - Lifetime lifting. That is, rather than have the trait methods take
&self
, have them takeself
andimpl
the trait on reference types. (That's more generally a way to encode HKTs.) Problem is, auto-deref and auto-borrowing for method receivers doesn't work for this kind of case: iffoo: Vec<T>
, you'd have to write(&foo)[]
to get a full slice, for example. You can hack around this in various ways, but it quickly gets pretty ugly.
I'm not sure if there's a design that would allow us to smoothly transition to an HKT type parameter later on. Worth thinking about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to use this with other unsized types as well (i.e., structs with embedded [T] fields).
How many such types will we have for which slicing makes sense? I can only think of minor variations on the 4 types above e.g. different allocators (which Vec<T, Allocator>
would solve anyway) and interned strings ala libsyntax
's InternedString
.
(My point here is this RFC isn't particularly flexible; it's really nice for the places it does work.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this to the list of things that HKT will allow us ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand exactly why HKT is invoked here. I just did some testing with these traits returning just S
and everything seemed to work fine.
Again, I am going to protest this RFC just like the Index
traits one because it does not support lazy evaluation, which is critical to making efficient mathematical libraries. In particular, it seems clear that if this specific design is kept, it will need to be supplemented by SliceSet
and SliceGet
like Index/IndexMut
should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SiegeLord The traits as given work fine. My point about HKT is that you'd need it to make the traits general enough to do what @huonw was describing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SiegeLord I wasn't around when the Index
traits were designed. Can you either point me to that earlier discussion, or explain your concerns in more detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SiegeLord were you referring to #111?
I would expect a common question about this syntax to be "Why did you choose Of the other two bits of syntax introduced, I would predict fewer questions. I would expect that the usage of brackets to be unanimously expected as brackets convey "slice" in rust right now. I think it's definitely necessary to have a mutable slicing variant, and I think that the syntax you've proposed is likely the best one available. |
@alexcrichton Updated with rationale. |
The `as_slice` operator is particularly important. Since we've moved away from | ||
auto-slicing in coercions, explicit `as_slice` calls have become extremely | ||
common, and are one of the | ||
[leading ergonomic/first impression](https://github.com/rust-lang/rust/issues/14983) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure that e.g. foo(some_string[]);
is better from a first impressions/beginners perspective, it's rather cryptic/hard-to-google (sigil like?) syntax, unlike foo(some_string.as_slice())
or just foo(some_string)
.
Awesome, looks good! I prefer |
Could you include a mention of what standard library types will implement these traits? |
I prefer .. range syntax, while : should always used as name:type. |
|
||
*Mutable slicing* | ||
|
||
- `foo[mut]` for `foo.as_mut_slice()` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This puzzles me. Why doesn't indexing use a[mut 0]
for this same effect? Is there a between-the-lines proposal here to make IndexMut
also require a mut
annotation? If not, why does this need an annotation and IndexMut
not?
Obligatory Python-inspired question, what about multiple dimensions and strides? Python has And a strided slice would be less efficient and not implementable as |
I have always wanted this sugar, but I always was afraid that it'd be implemented in this very restricted fashion, making it primarily useful for built-in types and A reference for the multi-dimenstional slicing is D's solution, which might be doable in Rust: http://dlang.org/operatoroverloading#Slice |
@SiegeLord It would be helpful if you could spell out the restrictions you're worried about here, and what you'd like to see instead. (Or if you've written about this previously, please post a link.) |
👍 @bluss I agree that this is mildly inconsistent with @SiegeLord The support here should not interfere with your ability to write |
@huonw A quick note regarding the more general version: I talked with @nikomatsakis about it this morning, and it should be possible to add a That should suffice to eventually handle striding, ropes, etc. I'll amend the RFC with these points and a few others soon. |
Well, here's a very simple illustration of what would be possible with 'better' operator overloading. This snippet (made very non-generic for illustrative purposes) adds two vectors elementwise and then slices them without allocating a temporary vector. Note how there is nothing to return the reference to in the struct Vector
{
data: Vec<f64>,
}
impl Vector
{
fn add<'l>(&'l self, other: &'l Vector) -> Adder<'l>
{
Adder{ lhs: self, rhs: other }
}
fn index(&self, idx: uint) -> f64
{
self.data[idx]
}
}
struct Adder<'l>
{
lhs: &'l Vector,
rhs: &'l Vector
}
impl<'l> Adder<'l>
{
fn index(&self, idx: uint) -> f64
{
self.lhs.index(idx) + self.rhs.index(idx)
}
fn slice_from(self, from: uint) -> Slicer<'l>
{
Slicer{ base: self, from: from }
}
}
struct Slicer<'l>
{
base: Adder<'l>,
from: uint
}
impl<'l> Slicer<'l>
{
fn index(&self, idx: uint) -> f64
{
self.base.index(self.from + idx)
}
}
fn main()
{
let a = Vector{ data: vec![1.0, 2.0, 3.0] };
let b = Vector{ data: vec![4.0, 5.0, 6.0] };
let res = a.add(&b).slice_from(1);
assert_eq!(res.index(0), 7.0);
assert_eq!(res.index(1), 9.0);
} |
@SiegeLord Thanks. This seems essentially the same as the point as @huonw raised. In both cases, the problem is that we need HKT in order to abstract over a type like See my comment above to @huonw, namely that we will be able to add this more general form of slice syntax later, after we have HKT, in a backwards-compatible way. I will add details to the RFC as soon as I can, but the basic point is that accepting this syntax now doesn't preclude us from doing the more general version later. |
Well, that's where my question comes in. I managed to create a fully functional library without HKT today (perhaps it could be more functional with HKT?) but it's not clear to me that HKT is necessary. Here are the relevant traits and implementations from my library: pub trait MatrixSlice
{
fn slice(self, start: uint, end: uint) -> Slice<Self>;
}
impl<T: MatrixShape>
MatrixSlice for
T
{
fn slice(self, start: uint, end: uint) -> Slice<T>
{
Slice::new(self, start, end)
}
}
pub trait MatrixShape
{
fn ncol(&self) -> uint;
fn nrow(&self) -> uint;
}
impl<'l>
MatrixShape for
&'l Matrix
{
...
} You'll note that I did specialize my pub trait MatrixSlice2<T>
{
fn slice2(self, start: uint, end: uint) -> T;
}
impl<T: MatrixShape + Collection>
MatrixSlice2<Slice<T>> for
T
{
fn slice2(self, start: uint, end: uint) -> Slice<T>
{
Slice::new(self, start, end)
}
} |
@SiegeLord That works because you're taking In general, though, we want indexing and slicing operations to take |
It has to be let a = vec![1u];
let s = a.as_slice().as_slice(); My by value |
@SiegeLord That's a problem with the rules for borrowing and temporaries, which will be fixed when the implementation of @kballard's RFC lands. |
@SiegeLord To be more clear, if we used let a = vec![1u];
let x = (&a)[0];
let s = (&a)[]; which seems less than ideal. I think with the HKT-based traits and better rules for temporaries (which we've already committed to doing), we should be able to have good ergonomics and still handle your and @huonw's examples. |
Alright, I'll defer until those things are implemented and maybe try to port my library away from |
Thinking about it, I don't see why we couldn't have Naively I would expect that if Then we're also consistent with |
Exclusive ranges are a lot easier to deal with: let slice = some_vec[a..a + 2];
assert_eq!(slice.len(), 2);
let m = some_vec.len() / 2;
// These two slices have no overlap
let slice1 = some_vec[..m];
let slice2 = some_vec[m..]; You'd have to sprinkle lots of |
I think slicing pretty much requires exclusive ranges. It's confusing otherwise. Honestly, maybe it's not really that bad to have |
Agreed with @kballard. |
@CloudiDust At this point, I think we should modify |
@kballard, I agree with you. |
agree with @kballard (his latest comment). |
Incidentally, I want to mention an important use case for exclusive ranges in matches (i.e. I'm suggesting allowing both // .. means exclusive here
match 1.0f32
{
0.0 .. 1.0 => (),
1.0 .. 2.0 => (),
_ => ()
} It is basically impossible to write the above match with inclusive ranges, so while we support floating point ranges, there is some impetus to providing exclusive ranges with matches. That said, floating point ranges are kind of... strange anyway, so an alternate solution would be the non-solution of nuking this problematic feature. |
I've amended the RFC to clarify some points discussed in the comments, and to change the |
👍, although I think the Summary should also include a mention of the |
@kballard Thanks, updated. |
6357402
to
e0acdf4
Compare
Discussed a few weeks ago and the decision was to merge this. |
Did this make it into the pre-1.0 Alpha? The reference manual doesn't mention it. Currently, where s is a "&str", the old way works but is deprecated:
The new way doesn't work yet.
Something seems to be out of sync. |
@John-Nagle |
OK. Still needs to go in the reference manual, though. |
This RFC adds overloaded slice notation:
foo[]
forfoo.as_slice()
foo[n..m]
forfoo.slice(n, m)
foo[n..]
forfoo.slice_from(n)
foo[..m]
forfoo.slice_to(m)
mut
variants of all the abovevia two new traits,
Slice
andSliceMut
.It also changes the notation for range
match
patterns to...
, tosignify that they are inclusive whereas
..
in slices are exclusive.Rendered
Active: https://github.com/rust-lang/rfcs/blob/master/text/0198-slice-notation.md