I finally got around to writing a teeny Rust program. As ever for me, I needed a itch that I could scratch before I could write something. I’ve dabbled in Rust before, but mostly as build fixes for NetBSD. Unfortunately/stupidly I wrote this at work so I can’t share it in full here, but I can write up my notes much like I did my Golang ones.

  • As a rough guide for my brain I like to think of Rust as an imperative version of Haskell, specifically Haskell via Stack
  • …because when creating new things you should use cargo to get started. I.e. cargo new my-project instead of just creating a new file with a *.rs extension and trying to use rustc.
  • The error messages when trying to build/compile are very Haskelly - yes, they probably do tell you exactly what is wrong, but in general they aren’t any help (and you will just end up searching) unless you are an expert and then you probably aren’t making those mistakes anyway; rustc --explain is cool though.
  • Need to use the keyword mut if you want a mutable variable.
  • The amount of dependencies scare me a bit. I used reqwest. Just adding that one dependency to my project resulted in about 90 crates being compiled. Maybe there is something lighter-weight than reqwest? Perhaps ureq. Nothing built-in? Also, I had to explicitly enable a blocking/non-async client by setting reqwest = { version = "0.11", features = ["json", "blocking"] } in the [dependencies] section of my Cargo.toml because I didn’t need the complexity of async; Although it seems difficult to actually escape.
  • A lot of Rust examples are out of date. This can be true for any language, but I guess Rust has evolved rapidly. E.g. I started looking at this example reqwest code, but found out error_chain was no more.
  • Box<dyn Error> is super useful, specifically using fn main() -> Result<(), Box<dyn Error>> otherwise you can’t get out of complier errors as you can’t appease all the expected error types.
  • The ? operator and error propagation stuff makes me think of Haskell’s Data.Maybe.
  • Related: the use of unwrap() for quick/lazy getting at of values is like Haskell’s fromJust. In both cases you probably shouldn’t really use it, but it’s fine for quick/little programs.
  • serde_json works really similar to Haskell’s Aeson. I like this a lot.
  • Especially using the #[derive(Serialize, Deserialize, Debug)] stuff:

      use serde::{Deserialize, Serialize};
    
      #[derive(Serialize, Deserialize, Debug)]
      struct NotificationRuleList {
          total: i32,
          notification_rules: Vec<NotificationRule>,
      }
        
      #[derive(Serialize, Deserialize, Debug)]
      struct NotificationRule {
          id: String,
          #[serde(rename = "type")]
          rule_type: String, // Needed to rename this field as `type` is a reserved keyword
          start_delay_in_minutes: i32,
          urgency: String,
          contact_method: ContactMethod,
      }
        
      #[derive(Serialize, Deserialize, Debug)]
      struct ContactMethod {
          id: String,
          #[serde(rename = "type")]
          method_type: String, // Needed to rename this field as `type` is a reserved keyword
      }
    
      [...]
    
      let response = client
      .get(format!(
          "https://api.pagerduty.com/users/{pd_userid}/notification_rules?urgency=low"
      ))
      .header(AUTHORIZATION, format!("Token token={pd_token}"))
      .header(ACCEPT, "application/json")
      .send()?
      .text()?;
    
      let notification_rule_list: NotificationRuleList = serde_json::from_str(&response)?;
      # Maybe there is probably a way to use `.json()` instead of `.text()` and go straight to `serde_json`?
    
      for mut notification_rule in notification_rule_list.notification_rules {
    
      [...]
    

    These examples are for parsing the PagerDuty notification rules response.

  • Using Debug is handy for printing out the structure: println!("deserialized = {:?}", notification_rule_list);.
  • I still don’t understand String vs &str. I just know you can’t pattern match on String so have to do match method_type.as_str().
  • The wiki page is a really good introduction and I should have read that first.
  • The book is also very good (already linked to bits of it here).
  • Cross compiling doesn’t seem to be as easy as Golang, although this could be because I was trying to go from macOS arm64 to NetBSD amd64:
    1. Need(?) rustup
    2. rustup target add x86_64-unknown-netbsd
    3. cargo build --target=x86_64-unknown-netbsd (rustc --print target-list is handy as wasn’t sure what it was called, had tried with amd64-unknown-netbsd)
    4. But then I hit errors with openssl
    5. So then I tried rustls-tls, but then hit:

       error occurred: Failed to find tool. Is `x86_64--netbsd-gcc` installed?`
      
    6. I gave up

That’s it for now, hope I manage to find reasons/excuses to do some more.


[EDIT: 2024-02-15] Added some further notes on cross-compilation.