Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I correctly implement std::iter::Step in Rust 1.25 nightly?

Tags:

rust

I am trying to implement a struct to represent a date, which can be used with range syntax (for d in start..end { }). I know there are already crates out there that handle dates, but I am doing this as an exercise.

Here is the struct:

type DayOfMonth = u8;
type Month = u8;
type Year = u16;

#[derive(PartialEq, Eq, Clone)]
pub struct Date {
    pub year: Year,
    pub month: Month,
    pub day: DayOfMonth
}

Here is how I would like to use it:

fn print_dates() {
    let start = Date { year: 1999, month: 1, day: 1 };
    let end = Date { year: 1999, month: 12, day: 31 };

    for d in start..end {
        println!("{}-{}-{}", d.year, d.month, d.day);
    }
} 

I originally tried implementing the Iterator trait, but then when I tried to use range syntax, I got a compiler error saying I needed to implement Step instead.

The documentation shows this signature for the Step trait.

pub trait Step: PartialOrd<Self> + Clone {
    fn steps_between(start: &Self, end: &Self) -> Option<usize>;
    fn replace_one(&mut self) -> Self;
    fn replace_zero(&mut self) -> Self;
    fn add_one(&self) -> Self;
    fn sub_one(&self) -> Self;
    fn add_usize(&self, n: usize) -> Option<Self>;
}

I've already implemented Ord and PartialOrd:

impl Ord for Date {
    fn cmp(&self, other: &Self) -> Ordering {
         match self.year.cmp(&other.year) {
            Ordering::Equal =>
                match self.month.cmp(&other.month) {
                    Ordering::Equal =>
                        self.day.cmp(&other.day),
                    ord => ord
                },
            ord => ord
        }
    }
}

impl PartialOrd for Date {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

I am implementing Clone automatically with #[derive(Clone)].

I started implementing Step, but there are some methods that I cannot figure out what to do with. Here is what I have so far:

impl Step for Date {
    fn steps_between(start: &self, end: &self) -> Option<usize> {
        //is_valid_date checks that the month is not > 12 and other rules like that
        if is_valid_date(start) && is_valid_date(end) {
            //get_epoch_day_number gets the number of days since 1900-01-01
            let diff = get_epoch_day_number(end) - get_epoch_day_number(start);
            Some(diff)
        }
        else { None }
    }

    fn add_one(&self) -> Self {
        //Try the next day
        let mut next = Date {
            year: self.year,
            month: self.month,
            day: self.day + 1
        };
        //If not valid, try the 1st of the next month
        if !is_valid_date(&next) {
            next = Date {
                year: self.year,
                month: self.month + 1,
                day: 1
            };
        }
        //If not valid, try the 1st of the next year
        if !is_valid_date(&next) { 
            next = Date {
                year: self.year + 1,
                month: 1,
                day: 1
            };
        }
        next
    }

    fn sub_one(&self) -> Self {
        //Try the prev day
        let mut prev = Date {
            year: self.year,
            month: self.month,
            day: self.day - 1
        };
        //If not valid, try the last of the prev month
        if !is_valid_date(&prev) {
            let m = self.month - 1;
            prev = Date {
                year: self.year,
                month: m,
                day: get_month_length(self.year, m)
            };
        }
        //If not valid, try the last of the prev year
        if !is_valid_date(&prev) {             
            prev = Date {
                year: self.year - 1,
                month: 12,
                day: 31
            };
        }
        prev
    }

    fn add_usize(&self, n: usize) -> Self {
        //This is really inefficient, but that's not important
        let mut result = self;
        for i in 1..n+1 {
            result = result.add_one();
        }
        result
    }

    fn replace_one(&mut self) -> Self {
        // ?
    }

    fn replace_zero(&mut self) -> Self {
        // ?
    }
}

I am really stumped on what replace_one and replace_zero are supposed to do. The documentation says:

Replaces this step with 1, returning itself. and Replaces this step with 0, returning itself.

Does my struct need to have zero and one identity values just to be used in a range? Shouldn't add_one be enough?

The wording used by the documentation is also a little unclear. If we replace x with 1 and return "itself", is "it" x or 1?

like image 911
JamesFaix Avatar asked Feb 04 '18 22:02

JamesFaix


1 Answers

I just looked at Rust's code where those methods are used. The only uses in whole of rustc's repository are to implement RangeInclusive operations. An empty RangeInclusive is represented as a range from 1 to 0, so the next, next_back and nth methods need to be able to get those somehow, and that's what replace_one and replace_zero are for.

I would suggest opening an issue on rustc's GitHub to make the documentation better, and possibly change the name of these methods.

like image 139
mcarton Avatar answered Nov 14 '22 02:11

mcarton