Skip to content

Tracking Issue for remainder methods for str split iterators #77998

Open

Description

@WaffleLapkin

View all comments

This is a tracking issue for the methods implemented in #75265 and #82570, which allow viewing the remainder of the underlying string in str-split iterators. E.g.:

let mut split = "Mary had a little lamb".split(' '); 
assert_eq!(split.remainder(), Some("Mary had a little lamb"));

_ = split.next();
assert_eq!(split.remainder(), Some("had a little lamb"));

split.by_ref().for_each(drop);
assert_eq!(split.remainder(), None);

The feature gates for the issue are #![feature(str_split_remainder)] (for most split iterators), #![feature(str_split_inclusive_remainder)] (for SplitInclusive) and #![feature(str_split_whitespace_remainder)] (for SplitWhitespace and SplitAsciiWhitespace).

Public API

// mod core::str
impl<'a, P: Pattern<'a>> Split<'a, P> {
    fn remainder(&self) -> Option<&'a str>;
}

// And the same for
// - `RSplit<'a, P>`
// - `SplitTerminator<'a, P>`
// - `RSplitTerminator<'a, P>`
// - `SplitN<'a, P>`
// - `RSplitN<'a, P>`
// - `SplitWhitespace<'a>`
// - `SplitAsciiWhitespace<'a>`
// - `SplitInclusive<'a, P>`

Steps

Unresolved Questions

Implementation history

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Oct 15, 2020
  2. added
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    T-libs-api[DEPRECATED; DO NOT USE]
    on Oct 15, 2020
  3. SkiFire13 commented on Oct 19, 2020

    @SkiFire13
    Contributor

    Looking at its name it isn't immediately clear what it returns. Wouldn't remainder or something along that line be a more self-explanatory name?

  4. WaffleLapkin commented on Oct 20, 2020

    @WaffleLapkin
    MemberAuthor

    The name as_str was chosen to be in line with Chars::as_str

  5. added
    I-libs-radarLibs issues that are tracked on the team's radar.
    on Nov 6, 2020
  6. TyPR124 commented on Feb 20, 2021

    @TyPR124
    Contributor

    Can this be added to SplitWhitespace and SplitAsciiWhitespace?

  7. WaffleLapkin commented on Feb 20, 2021

    @WaffleLapkin
    MemberAuthor

    @TyPR124 as noted in the implementation PR discussion it is possible, but not trivial.

    Both Split*Whitespace are using iterators internally which doesn't allow to access it's inner parts (^1, ^2). It's possible to 'solve' this problem either by making the fields pub(crate::str::iter) (tbh it's a hack) or providing methods on iterator adaptors that allow accessing inner iterators (downside: there are lots of adaptors to change).

    I didn't want to complicate the original PR with this, so didn't include anything like this. I can now make a new PR that'll add the same methods to Split*Whitespace, thanks for reminding about it!

  8. WaffleLapkin commented on Mar 20, 2021

    @WaffleLapkin
    MemberAuthor

    Update: #82570 was merged, so now SplitWhitespace and SplitAsciiWhitespace has as_str method too!

  9. soerenmeier commented on Apr 14, 2021

    @soerenmeier
    Contributor

    I just had a case we're i needed to split a str: "tag:value" into a tag: &str and a value: Option<&str>.
    I know i can just use Some(iter.as_str()).filter(str::is_empty) to get the Option, but maybe there could be another method named as_opt_str or remainder. That method would return None if calling next would also return None.
    What do you think?

  10. SkiFire13 commented on Apr 14, 2021

    @SkiFire13
    Contributor

    Note that split iterators could yield an empty str as their final item, so as_opt_str could return Some("") while your code with always filter that case.

  11. soerenmeier commented on Apr 14, 2021

    @soerenmeier
    Contributor

    That's true, to be correct the above code should look like this:

    let s = iter.as_str();
    iter.next().map(|_| s)
    

    Would be nice to have as_opt_str.

  12. m-ou-se commented on May 4, 2021

    @m-ou-se
    Member

    I just had a case we're i needed to split a str: "tag:value" into a tag: &str and a value: Option<&str>.

    That sounds like a use case for the recently stabilized split_once.

  13. 8 remaining items

  14. Piturnah commented on Jun 29, 2023

    @Piturnah
    Contributor

    What's the status wrt stabilisation?

  15. smarnach commented on Aug 5, 2023

    @smarnach
    Contributor

    I noticed that SplitWhitespace::remainder() has some counter-intuitive behaviour if tokens are separated by multiple whitespace characters. The remainder() method includes all but the first whitespace character in the returned string:

    let s = "foo \tbar";
    let mut it = s.split_whitespace();
    it.next().unwrap();
    assert_eq!(it.remainder(), Some("\tbar"));
    

    I would expect it to either include all of the whitespace or none of it (though I do understand why it behaves the way it does). This behaviour, if intended, should at least be documented.

  16. kornelski commented on Oct 21, 2023

    @kornelski
    Contributor

    I needed a functionality of "split the string once on X or Y, whichever is first", and I need to know whether X or Y was first.

    I hoped I could use str.split(|c: char| c == X || c == Y) and then inspect the iter.remainder() to see whether X or Y was there, but I was disappointed to learn that the remainder is after the next split, rather than immediately after the previously returned substring.

  17. added 2 commits that reference this issue on Jan 26, 2024
  18. added a commit that references this issue on Jan 26, 2024
  19. camsteffen commented on Mar 11, 2025

    @camsteffen
    Contributor

    I think as_str would be a fine name - I personally just expected it to exist since I am familiar with as_str and as_slice on other iterators.

    I feel like the Option may be overcomplicating it. I expected it to just return an empty string as in &string[string.len()..] == "". I think the reasoning for Option doesn't apply for split_inclusive() at least. Not having an Option would allow for simpler code when an empty string is not a special case.

  20. lo48576 commented on Mar 11, 2025

    @lo48576
    Contributor

    I feel like the Option may be overcomplicating it. I expected it to just return an empty string as in &string[string.len()..] == "". I think the reasoning for Option doesn't apply for split_inclusive() at least. Not having an Option would allow for simpler code when an empty string is not a special case.

    I think returning None is more convenient. Assume you got the remainder as iter.as_str().

    • None to empty string: iter.as_str().unwrap_or_default()
    • empty string to None: Some(iter.as_str()).filter(|s| !s.is_empty())
    • (identity conversion: iter.as_str())

    I don't remember if there is simpler or shorter way of doing Some(val).filter(pred) without binding to a variable, but from this comparison I think returning Option<&str> from the method is more generic and straightforward to use in any situation.

  21. sumitpsm commented on Nov 6, 2025

    @sumitpsm

    Here, is another use case of remainder function:
    I am trying to implement a Scanner

    pub struct Scanner<R> {
        reader: R,
        buffer: Vec<u8>,
        iter: std::str::SplitAsciiWhitespace<'static>,
    }
    
    impl<R: std::io::BufRead> Scanner<R> {
        pub fn new(reader: R) -> Self {
            Self {
                reader,
                buffer: vec![],
                iter: "".split_ascii_whitespace(),
            }
        }
    
        pub fn next<T: std::str::FromStr>(&mut self) -> T {
            loop {
                if let Some(token) = self.iter.next() {
                    return unsafe { token.parse().unwrap_unchecked() };
                }
                self.buffer.clear();
                self.reader.read_until(b'\n', &mut self.buffer).unwrap();
                self.iter = unsafe {
                    let slice = std::str::from_utf8_unchecked(&self.buffer);
                    std::mem::transmute(slice.split_ascii_whitespace())
                }
            }
        }
    
        pub fn next_line<T: std::str::FromStr>(&mut self) -> T {
            if self.iter.nth(0).is_none() {
                self.buffer.clear();
                self.reader.read_until(b'\n', &mut self.buffer).unwrap();
            }
            self.iter = "".split_ascii_whitespace();
            let result = std::mem::take(&mut self.buffer);
            unsafe {
                let s = std::str::from_utf8_unchecked(&result);
                s.trim().parse().unwrap_unchecked()
            }
        }
    }

    here remainder function will boil down the code for the next_line function in few lines

  22. cramertj commented on Jun 2, 2026

    @cramertj
    Member

    I have a use case for this where I want to split a file into a header and a footer, where the end of the header is determined by seeing a line that matches some predicate. I don't particularly care whether the remainder is empty or not, so in this case the Option would be mapped to a an empty string.

  23. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
  24. sffc commented on Sep 24, 2026

    @sffc

    I've landed on this issue multiple times after looking for a function like split_once_inclusive.

  25. removed
    I-libs-radarLibs issues that are tracked on the team's radar.
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-iteratorsArea: IteratorsA-strArea: str and StringB-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions