Various musings of Brock Wilcox (@awwaiid)

2020.09.04 Rusty Slides

To keep from getting rusty, and also because all the cool kids are doing it, I'm picking up a bit of Rust.

I remember at one point I was learning a few new programming languages pretty deeply every year or so and then all of a sudden a year went by when I thought "hmm. I didn't learn a new language this year! I'm getting soft!". Upon further reflection I realized that I was learning languages previously at a high rate by working on them 20+ hours a week instead of doing any homework in college. So then I started thinking it's not so much that I'm getting dumber or learning more slowly, but that I am learning completely different things like how to make real-world software or how to make a business effective at all levels and things like that. Ahh tradeoffs.

Annnnyyywaayy. Rust.

I had a lot of fun with OCaml, doing all sorts of things but especially building tiny toy programming languages and parsers. The strong types and type inferencing there are very cool, and I took great joy in trying to NOT put any explicit types in my code and let the language figure it out. Earn it's keep. If I accidentally write something more general-purpose, type-wise, than I intended, well so be it. OCaml would delight in telling me when I contradicted myself, though often the errors felt backwards. Part of this is because it was written by some cool Frenchies, but also because you'd often make a type-mistake first and a correct-type second. Then it'd get to the correct-type and complain because it was expecting the type-mistake, complaining "I expected an Int but here you used an Str" or similar. You think to yourself... well yeah I used a Str because that's what it's supposed to be. After a while you learned the language of its errors and it all gets much faster, but still.

So the cool kids told me that Rust has type inference and much better error messages. So far... it definitely has good error messages, but I'm not too impressed by the type inference. This is probably because I'm doing some real-world work or something.

The learning project that I've made the most progress on is porting Pinpoint, an old gtk-based slide-presentation software that I like, from C to Rust. I call it Pertinent. Well not really porting -- I tried porting and that didn't work out very well. So more like re-implementation. This program takes in a nice text file of slides separated by dashes like this:






-- [kittens.jpg] [fit]


and it displays "HELLO" and "WORLD" in big letters on the center of the screen as slide 1 and slide 2 and so on. It has some ways to indicate background image and some other markup, but that's the core of the thing.

First up is to parse the file, and for that I've turned to nom, a parser-combinator. This part of the project has gone pretty well, and has made for nicer code than the hand-written parser in the C code (though not TOTALLY dissimilar). Here's a snippet:

// Slide headers look like "-- [a] [b]\n"
fn header(input: &str) -> nom::IResult<&str, SlideOptions> {
    let option_list = many0(option);

    let (input, options) = terminated(
        preceded(many1(tag("-")), option_list),
        alt((whitespace_or_comment, line_ending)),

    let options = options.iter().map(|s| s.to_string()).collect();
    return Ok((input, options));

Now for unrelated reasons I did at one point make a Raku parser for this slide format. It is way prettier in Raku ... here is roughly the same thing (where "setting" is like the "option_list" kinda):

token header { ^^ "--" [ \h* '[' <setting> ']' ]* \h* \n }

So it's kinda like writing a regex out longhand in nom. This might be a little prettier in OCaml, but that is mostly because of needing less parenthesis and having more operators overloaded. Probably there is some macro sugar in Rust that could make this shorter.

More interesting to read is the data structure that I'm parsing this thing into.

type SlideOptions = Vec<String>;

#[derive(Debug, PartialEq)]
pub struct Slide {
    options: SlideOptions,
    content: String,

#[derive(Debug, PartialEq)]
pub struct SlideDeck {
    global_options: SlideOptions,
    slides: Vec<Slide>,

Pretty cool. Have a nicely readable SlideDeck, individual Slide, and a list of options. The derive stuff is neat too, kinda auto-generates functions to pair with the datastructures. Recent OCaml (or libs) have some helpers for this sort of thing too, and they are pretty common in Haskell I think.

You can see the current Pertinent parser on github!

Next up is turning out to be the hard bit, actually displaying the slides. Pinpoint uses Gtk and Cairo with Pango markup. Looks like I have all of those things already all wrapped up in Rust crates (libraries) and I'm making progress. I don't really plan on implementing all the features of Pinpoint, and I MIGHT not ever actually USE this application. But it's fun anyway :)


Blog Blog RSS Feed







Toots (Mastodon)




Follow @awwaiid

Wiki Edits Wiki RSS Feed

... more changes