I think str::split_once
has the wrong signature (what is the point of using a heterogeneous datatype that only contains the same type???), there will always be at least one string returned by str::split
, so this should be reflected in the signature:
fn split_once<'a, P: Pattern<'a>>(&'a self, delimiter: P) -> (&'a str, Option<&'a str>);
but this function has been stabilized already, so there is no point in discussing that.
Instead, I would suggest changing the proposed signature to something like this:
pub fn str::split_at_most<'a, P, const N: usize>(&'a self, pat: P) -> [Option<&str>; N]
where
P: Pattern<'a>;
which is in my opinion more useful.
For my personal use I created an extension trait that looks something like this:
#![feature(pattern)]
use core::str::pattern::Pattern;
pub trait StrExt<'a> {
fn split_once<P: Pattern<'a>>(&self, pattern: P) -> (&'a str, Option<&'a str>);
fn split_at_most<P, const N: usize>(&self, pattern: P) -> [Option<&'a str>; N]
where
P: Pattern<'a>;
}
impl<'a> StrExt<'a> for &'a str {
fn split_once<P: Pattern<'a>>(&self, pattern: P) -> (&'a str, Option<&'a str>) {
if let [Some(first), second] = self.split_at_most::<_, 2>(pattern) {
(first, second)
} else {
unreachable!("split_at_most must return at least one element")
}
}
fn split_at_most<P, const N: usize>(&self, pattern: P) -> [Option<&'a str>; N]
where
P: Pattern<'a>,
{
let mut iter = self.splitn(N, pattern);
[(); N].map(|_| iter.next())
}
}
Personally, I would prefer if one were to wait for some kind of generic tuples, so you could have this signature:
fn split_at_most<P: Pattern, const N: usize>(&self, pattern: P) -> (&str, Option<&str>..{ N - 1 });
Here is an example that uses those functions:
use core::time::Duration;
use super::ParserError;
trait DurationExt {
#[inline]
#[must_use]
fn from_hours(hours: u64) -> Duration { Duration::from_mins(hours * 60) }
#[inline]
#[must_use]
fn from_mins(mins: u64) -> Duration { Duration::from_secs(mins * 60) }
}
impl DurationExt for Duration {}
fn parse_u64(string: &str) -> Result<u64, ParserError> {
string
.parse::<u64>()
.map_err(|e| ParserError::parse_int_error(e, 0..string.len()))
}
/// Parses a `String` of the following format:
/// `hours:minutes:seconds,milliseconds`
pub fn parse_duration(input: &str) -> Result<Duration, ParserError> {
if let [Some(hours), Some(minutes), Some(seconds_millis)] = input.split_at_most::<_, 3>(':') {
if let (seconds, Some(millis)) = seconds_millis.split_once(',') {
let mut result = Duration::from_secs(0);
result += Duration::from_hours(parse_u64(hours)?);
result += Duration::from_mins(parse_u64(minutes)?);
result += Duration::from_secs(parse_u64(seconds)?);
result += Duration::from_millis(parse_u64(millis)?);
return Ok(result);
}
}
Err(ParserError::invalid_duration(0..input.len()))
}
Note: This does not take advantage of the fact that [Option<&str>; N]
is returned instead of Option<[&str; N]>
, but this could be easily changed by replacing the if let
with a match
to report better errors:
/// Parses a `String` of the following format:
/// `hours:minutes:seconds,milliseconds`
pub fn parse_duration(input: &str) -> Result<Duration, ParserError> {
match input.split_at_most::<_, 3>(':') {
[Some(hours), Some(minutes), Some(seconds_millis)] => {
if let (seconds, Some(millis)) = seconds_millis.split_once(',') {
let mut result = Duration::from_secs(0);
result += Duration::from_hours(parse_u64(hours)?);
result += Duration::from_mins(parse_u64(minutes)?);
result += Duration::from_secs(parse_u64(seconds)?);
result += Duration::from_millis(parse_u64(millis)?);
Ok(result)
} else {
Err(ParserError::InvalidSeconds)
}
}
[Some(hours), None, None] => Err(ParserError::invalid_duration(0..input.len())),
[Some(hours), Some(minutes), None] => Err(ParserError::MissingSeconds),
}
}