Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: overloaded slice notation #198
Conversation
This comment has been minimized.
This comment has been minimized.
metajack
commented
Aug 14, 2014
|
I like this. +1 |
This comment has been minimized.
This comment has been minimized.
metajack
commented
Aug 14, 2014
|
It occurs to me that other languages tend to use |
This comment has been minimized.
This comment has been minimized.
|
Super Also, in Ruby, |
This comment has been minimized.
This comment has been minimized.
|
I think I agree that I think inclusive/exclusive is probably overkill here. |
This comment has been minimized.
This comment has been minimized.
metajack
commented
Aug 14, 2014
|
Python uses |
This comment has been minimized.
This comment has been minimized.
Valloric
commented
Aug 14, 2014
|
+1. Would love to see this! |
huonw
reviewed
Aug 14, 2014
|
|
||
| ```rust | ||
| trait Slice<Idx, S> { | ||
| fn as_slice<'a>(&'a self) -> &'a S; |
This comment has been minimized.
This comment has been minimized.
huonw
Aug 14, 2014
Member
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 stepth 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.
This comment has been minimized.
This comment has been minimized.
aturon
Aug 14, 2014
Author
Member
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
StridedSlicefor example. - Lifetime lifting. That is, rather than have the trait methods take
&self, have them takeselfandimplthe 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.
This comment has been minimized.
This comment has been minimized.
huonw
Aug 14, 2014
Member
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.)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
SiegeLord
Aug 14, 2014
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.
This comment has been minimized.
This comment has been minimized.
aturon
Aug 14, 2014
Author
Member
@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.
This comment has been minimized.
This comment has been minimized.
aturon
Aug 14, 2014
Author
Member
@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?
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2014
|
|
||
| # Alternatives | ||
|
|
||
| For improving the ergonomics of `as_slice`, there are two main alternatives. |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 14, 2014
Member
All of the methods on slices currently come through traits, so another possible alternative would be implementing the traits for String and Vec directly? I would expect all methods would be default in terms of as_slice(), so the trait implementations for Vec and String wouldn't be too lengthy.
This would only solve half the problem, though. Calling methods would be much nicer, but passing arguments would still require a manual .as_slice(). I think I prefer slicing syntax over this, but it may be worth considering as an alternative.
This comment has been minimized.
This comment has been minimized.
aturon
Aug 14, 2014
Author
Member
Yes, my hope is that we do not implement Deref on e.g. Vec, and instead add this slicing notation and implement the slice traits directly on Vec as you're suggesting.
But the ergonomics issue I'm most worried about is the need for as_slice in arguments.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton Updated with rationale. |
huonw
reviewed
Aug 14, 2014
| 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) |
This comment has been minimized.
This comment has been minimized.
huonw
Aug 14, 2014
Member
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).
huonw
reviewed
Aug 14, 2014
| severely limiting the ability of programmers to usefully implement new traits | ||
| for `Vec`. | ||
|
|
||
| * The idea of `Vec` as a smart pointer around a slice, and the use of `&*v` as |
This comment has been minimized.
This comment has been minimized.
huonw
Aug 14, 2014
Member
I personally rather like it... (Something like: a Vec<T> is just a DST sequence type with two words of metadata rather than one like &[T], with Vec being the pointer, like Box.)
This comment has been minimized.
This comment has been minimized.
aturon
Aug 14, 2014
Author
Member
I'm not necessarily opposed, but as I tried to explain in the RFC, there may be very practical reasons not to implement Deref on Vec<T>, depending on our final resolution rules.
My point is just that if we can solve the ergonomic issues without tying it to Deref, we'll have more freedom for the design, even if we do ultimately go the Deref route.
This comment has been minimized.
This comment has been minimized.
|
Awesome, looks good! I prefer |
This comment has been minimized.
This comment has been minimized.
|
Could you include a mention of what standard library types will implement these traits? |
This comment has been minimized.
This comment has been minimized.
|
I prefer .. range syntax, while : should always used as name:type. |
SiegeLord
reviewed
Aug 14, 2014
|
|
||
| *Mutable slicing* | ||
|
|
||
| - `foo[mut]` for `foo.as_mut_slice()` |
This comment has been minimized.
This comment has been minimized.
SiegeLord
Aug 14, 2014
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?
This comment has been minimized.
This comment has been minimized.
bluss
commented
Aug 14, 2014
|
Obligatory Python-inspired question, what about multiple dimensions and strides? Python has And a strided slice would be less efficient and not implementable as |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 14, 2014
|
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 |
This comment has been minimized.
This comment has been minimized.
|
@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.) |
This comment has been minimized.
This comment has been minimized.
|
@bluss I agree that this is mildly inconsistent with @SiegeLord The support here should not interfere with your ability to write |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 14, 2014
|
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);
} |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 14, 2014
|
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)
}
} |
This comment has been minimized.
This comment has been minimized.
|
@SiegeLord That works because you're taking In general, though, we want indexing and slicing operations to take |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 14, 2014
|
It has to be let a = vec![1u];
let s = a.as_slice().as_slice();My by value |
This comment has been minimized.
This comment has been minimized.
|
@SiegeLord That's a problem with the rules for borrowing and temporaries, which will be fixed when the implementation of @kballard's RFC lands. |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 14, 2014
|
Alright, I'll defer until those things are implemented and maybe try to port my library away from |
This comment has been minimized.
This comment has been minimized.
|
Thinking about it, I don't see why we couldn't have Naively I would expect that if Then we're also consistent with |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 25, 2014
|
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 |
This comment has been minimized.
This comment has been minimized.
|
I think slicing pretty much requires exclusive ranges. It's confusing otherwise. Honestly, maybe it's not really that bad to have |
This comment has been minimized.
This comment has been minimized.
|
Agreed with @kballard. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@CloudiDust At this point, I think we should modify |
This comment has been minimized.
This comment has been minimized.
|
@kballard, I agree with you. |
This comment has been minimized.
This comment has been minimized.
|
agree with @kballard (his latest comment). |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Aug 27, 2014
|
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. |
This comment has been minimized.
This comment has been minimized.
|
I've amended the RFC to clarify some points discussed in the comments, and to change the |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
@kballard Thanks, updated. |
alexcrichton
assigned
aturon
Sep 4, 2014
This was referenced Sep 11, 2014
aturon
referenced this pull request
Sep 11, 2014
Closed
Make String/str, Vec/[] conversions more ergonomic #14983
alexcrichton
merged commit c233ec7
into
rust-lang:master
Sep 11, 2014
alexcrichton
force-pushed the
rust-lang:master
branch
from
6357402
to
e0acdf4
Sep 11, 2014
This comment has been minimized.
This comment has been minimized.
|
Discussed a few weeks ago and the decision was to merge this. |
anshin
referenced this pull request
Sep 16, 2014
Closed
Feature request: Automatically convert String to &str when required. #17300
oli-obk
referenced this pull request
Nov 3, 2014
Closed
mathematical interval notation for slice syntax #432
This comment has been minimized.
This comment has been minimized.
John-Nagle
commented
Jan 26, 2015
|
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. |
This comment has been minimized.
This comment has been minimized.
|
@John-Nagle |
This comment has been minimized.
This comment has been minimized.
John-Nagle
commented
Jan 26, 2015
|
OK. Still needs to go in the reference manual, though. |
aturon commentedAug 14, 2014
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)mutvariants of all the abovevia two new traits,
SliceandSliceMut.It also changes the notation for range
matchpatterns 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