#![warn(rust_2018_idioms)] | |
#[derive(Debug)] | |
pub struct StrSplit<'haystack, D> { | |
remainder: Option<&'haystack str>, | |
delimiter: D, | |
} | |
impl<'haystack, D> StrSplit<'haystack, D> { | |
pub fn new(haystack: &'haystack str, delimiter: D) -> Self { | |
Self { | |
remainder: Some(haystack), | |
delimiter, | |
} | |
} | |
} | |
pub trait Delimiter { | |
fn find_next(&self, s: &str) -> Option<(usize, usize)>; | |
} | |
impl<'haystack, D> Iterator for StrSplit<'haystack, D> | |
where | |
D: Delimiter, | |
{ | |
type Item = &'haystack str; | |
fn next(&mut self) -> Option<Self::Item> { | |
let remainder = self.remainder.as_mut()?; | |
if let Some((delim_start, delim_end)) = self.delimiter.find_next(remainder) { | |
let until_delimiter = &remainder[..delim_start]; | |
*remainder = &remainder[delim_end..]; | |
Some(until_delimiter) | |
} else { | |
self.remainder.take() | |
} | |
} | |
} | |
impl Delimiter for &str { | |
fn find_next(&self, s: &str) -> Option<(usize, usize)> { | |
s.find(self).map(|start| (start, start + self.len())) | |
} | |
} | |
impl Delimiter for char { | |
fn find_next(&self, s: &str) -> Option<(usize, usize)> { | |
s.char_indices() | |
.find(|(_, c)| c == self) | |
.map(|(start, _)| (start, start + self.len_utf8())) | |
} | |
} | |
pub fn until_char(s: &str, c: char) -> &'_ str { | |
StrSplit::new(s, c) | |
.next() | |
.expect("StrSplit always gives at least one result") | |
} | |
#[test] | |
fn until_char_test() { | |
assert_eq!(until_char("hello world", 'o'), "hell"); | |
} | |
#[test] | |
fn it_works() { | |
let haystack = "a b c d e"; | |
let letters: Vec<_> = StrSplit::new(haystack, " ").collect(); | |
assert_eq!(letters, vec!["a", "b", "c", "d", "e"]); | |
} | |
#[test] | |
fn tail() { | |
let haystack = "a b c d "; | |
let letters: Vec<_> = StrSplit::new(haystack, " ").collect(); | |
assert_eq!(letters, vec!["a", "b", "c", "d", ""]); | |
} |
The ?
in 2 and 4 copies the entire Option
when the inner type is Copy
. What we then take a mutable reference to is what is inside of that copy, not the original Option
. In the last case, we turn Option<T>
into Option<&mut T>
, which, when copied, still yields a mutable reference into the original Option
. Does that help?
yes, thanks. I wish there was a way to turn off or at least warn about implicit switch from move to copy semantics. Is there a way to guard against this kind of bugs?
Hmm, it's a good question. I wonder if maybe clippy could warn about this somehow. In theory the information may be available to determine it statically.
Hey @jonhoo, how common is it to see or do:
impl Trait for &Type {...} //?
// such as
impl Delimiter for &str {...}
Additionally, if you intend to use both Type
and &Type
as implementers of Trait
, for instance:
let x1: Vec<_> = StrSplit::new(haystack, MyOtherDelimiterFlavor::new(...)).collect();
let x2: Vec<_> = StrSplit::new(haystack, &MyOtherDelimiterFlavor::new(...)).collect();
would one then need to write redundant impl
blocks, or is there some syntactic sugar for controlling this?
Thanks
It's actually fairly common, precisely to improve the ergonomics of using the trait as you indicate. In general, if you implement a trait that only takes &self
it's pretty reasonable to implement the trait for &MyType
, &mut MyType
and Box<MyType>
. If a trait method takes &mut self
, skip &MyType
. But of course, the downside is that now if the trait changes in the future, more breakage will ensure since your consumers expected to be able to transparently use &
(or &mut
).
Sorry if this was already asked elsewhere, but I am still not sure why:
I see that if a type is Copy, it gets copied instead of moved. But aren't we dealing with same types in 1 vs 2, 3 vs 4?