In my earlier blog post about building an inky wHat dashboard I mentioned using Rust to parse the calendar feed from my Google Calendar. To get a list of events upcoming events I had previously been using a Go library which parsed the ISC file from Google Calendar however there didn’t seem to be a way to make it output repeating event occurrences. This meant that my weekly reminder to put the bins out was not being displayed on the dashboard. To fulfil this need and satisfy my curiosity I decided to build my own.
Having done some research into Rust and learning about some of its benefits I wanted to give it a go. Rust is a lower level programming language than I am used to, I have no C/C++ experience and have never needed to manage memory directly, instead I have been reliant on a garbage collector. Rust’s memory management doesn’t work in the same way as either C++ or C#, it uses the borrow checker to make its memory safety guarantees.
One of the really neat features of Rust, that it shares with Go, is its ability to cross platform compile code between different architectures. In this case I should have been able to compile for ARM on my windows development machine and then copy over a binary ready to run on the Pi. However I ran into some difficulties compiling one of the dependencies for ARM and ended up installing Rust on my Pi, copying my project’s files over and then compiling it there when I was ready.
To get stuck in and began my journey as a Rustacean I checked out the impressive documentation and getting started guides.
I had planned on using nom to parse the ISC but using it proved a bit too difficult for someone who isn’t particularly well-versed in creating parsers, macros, nor in Rust. With this in mind I decided to solve the problem in a way more familiar to me, AKA hacking on it and seeing what works and what doesn’t.
I soon learnt that the Rust compiler is not forgiving and beat me in to submission on several occasions. Having said that, it is helpful and gives actionable information on what you need to do to fix the various compile errors.
The abridged description of the ISC format, enough for my purposes at least is as follows:
- 1 key value pair per line
- The key is separated from value by a colon “:”
- The file is separated into several sections which are nested
- Sections are encapsulated by
ENDkeys, the value for both of these is the “title” for that section
An actual calendar event looks similar to the following:
BEGIN:VEVENT DTSTART;TZID=Europe/London:20181114T212500 DTEND;TZID=Europe/London:20181114T213000 RRULE:FREQ=WEEKLY;WKST=MO DTSTAMP:20181229T182825Z UID:[email protected] CREATED:20181108T072208Z DESCRIPTION: LAST-MODIFIED:20181108T072208Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Bins TRANSP:OPAQUE BEGIN:VALARM ACTION:EMAIL DESCRIPTION:This is an event reminder SUMMARY:Alarm notification ATTENDEE:mailto:[email protected] TRIGGER:-P1D END:VALARM BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:This is an event reminder TRIGGER:-P0DT0H15M0S END:VALARM END:VEVENT
I parse the whole file but really only use a couple of the keys:
RRULE- Repetition rule
DTSTART- Event start date
DTEND- Event end date
SUMMARY- Event title
FYI there is no error handling in my parser so any malformed ISC will cause the app to crash and burn.
The Code / Learning Rust
I made a start on the code pretty much straight away but soon realised that I need to run through the intro guide and some hello world type exercises a few times. Rust is really different to anything I have written before, and the borrow checker caused me a fair bit of grief before I got to grips (in some small way!) with how it works.
I started with my code all in one file, but that was getting unmanageable pretty quickly so I wanted to split it out into separate files, similar to how you would in any other languages. For some reason I struggled with this and I am not sure that I really understand how things work, it was pretty much trial and error to get things how I wanted them (Hopefully someone can tell me what I was doing wrong in the comments!).
I have dabbled a little with functional programming in Scheme and a little F# but am by no means an expert! This project contains both code in OO and functional styles. I enjoyed using
filter_map. I also thought that the
match expressions were super useful and really powerful.
Dates / Times (whydidichooseaprojectwithdates)
The defacto date/time library for Rust programmers is “chrono”, I was surprised that there wasn’t an in-built datatype. Unfortunately it isn’t quite as feature complete as Go/C#/Python and the date addition left me needing to put some hacks in place. Therefore this code is not ready for production use. I do not understand the intricacies of dates and times so I wrote only what would work for me and my very few test cases. I completely ignore time-zones in my code as they were not really necessary in my case (NB this might change once daylight savings [🤮] comes around).
Sure enough BST introduced some bugs into my little setup, so I did have to tinker a little with timezones. No doubt there are some more that I have missed but I haven’t noticed any others yet.
So Rust is fast, in this case the Rust parser I wrote is faster than the Go library I had been using, significantly so. However, the test cases I ran through were far from conclusive and lacked proper scientific process no doubt.
Rust is really cool and I enjoyed this project, however the date time support was a little frustrating. I have barely scratched the surface of Rust’s capabilities and there is a lot of other stuff to learn, such as adding parallelism with Rayon and using traits. The Rust language documentation and crate documentation has been very useful when learning new functionality. There are lots of Rustaceans on Twitter and it has been great to see how helpful the community is there as well as on the rust subreddit.