Here is a solution without unsafe
.
It provides the same effect as s.chars().collect::<Vec<_>>().into_iter()
, but without the allocation overhead.
Further, it probably as fast as it's possible to get it. It doesn't reallocate, doesn't iterate repeatedly, it simply steps from character to character, in O(1)
for every step, giving you a total iteration of O(n)
. This is at the same time the lower bound of iterating over anything.
On top of it it isn't self-referential. So this approach is probably what you want, it combines all the advantages of the other answers and doesn't have any drawbacks.
struct OwnedChars {
s: String,
index: usize,
}
impl OwnedChars {
pub fn new(s: String) -> Self {
Self { s, index: 0 }
}
}
impl Iterator for OwnedChars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
// Slice of leftover characters
let slice = &self.s[self.index..];
// Iterator over leftover characters
let mut chars = slice.chars();
// Query the next char
let next_char = chars.next()?;
// Compute the new index by looking at how many bytes are left
// after querying the next char
self.index = self.s.len() - chars.as_str().len();
// Return next char
Some(next_char)
}
}
Together with a little bit of trait magic:
trait StringExt {
fn into_chars(self) -> OwnedChars;
}
impl StringExt for String {
fn into_chars(self) -> OwnedChars {
OwnedChars::new(self)
}
}
You can do:
struct Chunks {
remaining: OwnedChars,
}
impl Chunks {
fn new(s: String) -> Self {
Chunks {
remaining: s.into_chars(),
}
}
}
Chunks
generic over the iterator type:struct Chunks<Iter> {...}
,impl<Iter: Iterator<Item = char>> Chunks<Iter> {..}
,fn new<IntoIter: IntoIterator<Item = char>>(iter: IntoIter) ->Self {...}
. Then it doesn't care at all about where the chars come from; it could be owned, borrowed, whatever.