rust-lang / libs-team

The home of the library team
Apache License 2.0
115 stars 18 forks source link

Implement `into_chars` for `String` #268

Closed al-yisun closed 11 months ago

al-yisun commented 11 months ago

Proposal

Problem statement

Currently there is no owning analog to the chars iterator. In most contexts this has no real effect, as char is Copy, but it disables some cases where one wants to pass ownership of the underlying String along with the iterator.

Motivating example

Implementing the following function requires some gymnastics.

fn get_chars() -> impl Iterator<Item=char> {
    let s = format!("for one reason or another, you have a String");
    s.chars().filter(|c| ['a','e','i','o','u'].contains(c))
    // error[E0597]: `s` does not live long enough
}

Solution sketch

If into_chars existed, the above function would be simple to write.

fn get_chars() -> impl Iterator<Item=char> {
    let s = format!("for a very good reason, you have a String");
    s.into_chars().filter(|c| ['a','e','i','o','u'].contains(c))
}

Alternatives

It is relatively straightforward to write an owning chars iterator outside of std. As a struct:

struct IntoChars {
    string: String,
    position: usize,
}

impl Iterator for IntoChars {
    type Item = char;

    fn next(&mut self) -> Option<char> {
        let c = self.string[self.position..].chars().next()?;
        self.position += c.len_utf8();
        Some(c)
    }
}

As a closure:

fn into_chars(s: String) -> impl Iterator<Item = char> {
    let mut i = 0;
    std::iter::from_fn(move ||
        if i < s.len() {
            let c = s[i..].chars().next().unwrap();
            i += c.len_utf8();
            Some(c)
        } else {
            None
        }
    )
}

Links and related work

Discussion on irlo.

Amanieu commented 11 months ago

We discussed this in the libs-api meeting yesterday and couldn't think of any other way to achieve this functionality. As such it's fine to add this as an unstable API.