Skip to content
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

Non-error slicing #3436

Open
willtennien opened this issue May 24, 2023 · 8 comments
Open

Non-error slicing #3436

willtennien opened this issue May 24, 2023 · 8 comments

Comments

@willtennien
Copy link

In Python and JavaScript, there's a function vec.slice that never throws an error:

vec.slice(x, -y)

means "remove the first x elements from the start and the first y elements from the end". Rust currently throws an error if x is greater than vec.len() - 1. It returns an empty vec when the span includes no elements: that's what you want for many use cases.

It's convenient to do Python/JavaScript like accesses as an idiomatic version of takeing and droping (in the Haskell sense) in Rust.

For clarity, here is an example implementation:

fn slice_vec<T>(vec: &[T], start: isize, end: isize) -> &[T] {
    let len = vec.len() as isize;
    let start = if start >= 0 { start } else { len + start };
    let end = if end >= 0 { end } else { len + end };
    let start = start.max(0) as usize;
    let end = end.min(len).max(start) as usize;
    &vec[start..end]
}
@willtennien willtennien changed the title No-error slicing Non-error slicing May 24, 2023
@digama0
Copy link
Contributor

digama0 commented May 24, 2023

The use of isize looks really unergonomic to me, since both arguments in that example are unsigned (more precisely, start and -end are always nonnegative in the interesting case). Using an unsigned input simplifies some of those branches, and you can use saturating_sub if you are doing a subtraction to produce the inputs to the function and want to stop at 0.

@FHTMitchell
Copy link

FHTMitchell commented Jun 21, 2023

Python's signed slicing can cause some real gotchas, like what do you think this returns for b=""?

def endswith(a: str, b: str) -> bool:
     return a[-len(b):] == b

A better approach would be a special const END value that allows you to do slicing like vec[0..END-1]

#[derive(Clone, Copy, ...)]
pub struct FromEnd(usize);
pub const END: FromEnd = FromEnd(0);
impl SliceIndex for FromEnd { .. };
impl Sub<usize> for FromEnd { .. };
// etc

@SOF3
Copy link

SOF3 commented Jun 22, 2023

@FHTMitchell ops::Range requires both fields to have the same index. Implementing this would probably require a new conversion trait:

  1. Declare trait IntoRangeBound<T>, implement the blanket T: IntoRangeBound<T>
  2. Implement usize: IntoRangeBound<FromStartOrEnd>
  3. Desugar a..b as ops::Range { start: IntoRangeBound::into(a), end: IntoRangeBound::into(b) }

Type inference seems to work: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c067c8bf3600fc6c2e5f1c4411b7a4aa

@FHTMitchell
Copy link

@SOF3 Ah good point. Such a conversion trait would probably break inference though :(

How would the compiler know you want 0usize..5usize to be IntoRangBound<usize> or IntoRangeBound<FromStartOrEnd>?

@SOF3
Copy link

SOF3 commented Jun 23, 2023

what about just using an enum with Start and End variants? there's no strict need to make it work with two different types together.

@Aloso
Copy link

Aloso commented Jul 12, 2023

In my experience, most of the time when .slice() is used in JS, you only want to remove items from the start OR the end, but not both. So two functions – slice.remove_first(n) and slice.remove_last(n) would suffice for most use cases.

These names are pretty much self-explaining, while the syntax proposed in the RFC could be very confusing. That something.slice(0, -5) removes the last 5 elements is far from obvious to anyone not familiar with JS or Python.

If you want to remove items both from the start and the end of the slice, you could write something.remove_first(m).remove_last(n) – which is less concise than something.slice(m, -n), but I think it is easier to understand.

@SOF3
Copy link

SOF3 commented Jul 13, 2023

Should this issue be named signed slicing instead of non-error slicing?

@A248
Copy link

A248 commented Sep 8, 2023

Python's string slicing is so fabled for its error-prone, surprising behavior that university professors, in my experience, use it as prime example of a poorly-designed API.

A feature like this one is best experimented with by publishing a public crate. @FHTMitchell posted an example of how this might work without changes to the standard library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants