pho ramen soba

Friday, 6 September 2024

my personal adventures in rust — pistat

I'm not here to proselytize about Rust the language. There are too many out there already who will do that. I'm here to talk about how I've come to learn the language, what various tools and utilities I've written with it, and perhaps try to give an unbi…
Read on blog or Reader
Site logo image Arcane Science Lab Read on blog or Reader

my personal adventures in rust — pistat

By whbeebe on September 6, 2024

rust logoI'm not here to proselytize about Rust the language. There are too many out there already who will do that. I'm here to talk about how I've come to learn the language, what various tools and utilities I've written with it, and perhaps try to give an unbiased (or maybe even biased) opinion of the language. And I have example code to go along with this particular post.

Rust Background

Rust came into existence in 2006 as a Mozilla internal project. As the years went by it evolved to the point where it was self hosting and picked up a lot of features that are found in other languages. Because Rust was initially a part of Mozilla, that meant it was a victim of Mozilla's various vicissitudes, include a big layoff at Mozilla in 2020 that had a negative impact on Rust. As a consequence the Rust Foundation was created in February 2021. One of the founding members of the Rust Foundation was Google, who announced at the time that Rust would be an alternative language for writing Android applications. The Rust Foundation itself went through some bumps as well, which you can read about elsewhere. All I can say at this point is that I hope that drama has passed.

Rust has been described as a C-style language targeted at "frustrated C++ developers," that emphasizes features such as safety, control of memory layout, and concurrency. Because Rust started out as being written in OCaml, a functional programming language, there is a lot of that kind of influence in the design of the language. I have worked with 'pure' functional languages in decades past, so I can see some of that influence.

Personal Background

Let me take a moment to talk about "frustrated C++ developers." I count myself as one of them; I know all too well what many of those issues are. I've had to deal with them since I learned C in 1982 with Lifeboat C on an IBM XT, through C++ (C with classes) via cfront on a DEC MicroVAX II and DEC Ultrix in 1986. I then switched to Borland C++ in 1990, and I've been heavily invested in that language ever since across multiple operating systems. That's over 40 years of C and C++ development, many hundreds of thousands of lines of code scattered across dozens and dozens of for-pay application development as well as personal work. That didn't stop me from learning other languages, such as Java (1995) and Python (1993). I've tinkered with additional C-style languages, most significantly Google Go, but I've always dropped back to C++ for my heavy coding lifting needs, regardless of the warts on C++. As they say, better the devil you know.

Through the years and decades I've tried to adhere to best C++ coding standards, and I've tried various code analysis tools to ferret out "code smells" that code reviews would miss (no human code review is ever perfect, and turns out to be a waste of human's time; use automation and tools for consistency). I've tried to keep up with the latest official C++ coding standards, such as C++23, and use those compilers (such as g++ and clang) that implement those official coding standards. But it's tough, and as the standards have been released, the language has just grown more complex.

The Application

First, an important warning to the reader: DO NOT take this as idiomatic Rust source code. Regardless, it does work.

I wrote a non-trivial (to-me anyway) Rust-based utility on my Raspberry Pi 5 running Ubuntu 24.04.1 for the express purpose of producing a limited set of statistics about the running system. There's plenty of utilities for that already, but they produce too much information for my needs and/or have too many bells and whistles I can do without. What follows is an example of the statistics output I wanted.

   Raspberry Pi model:   Raspberry Pi 5 Model B Rev 1.0   Core model / count:   Cortex-A76 / 4   Kernel version:       6.8.0-1010-raspi   Operating system:     Ubuntu 24.04.1 LTS   Memory total / free:  7.75 Gi / 3.28 Gi   Swap total / free:    1.00 Gi / 1.00 Gi   Filesystem capacity:  468.9 Gi   Filesystem available: 432.1 Gi   Uptime:               8 days, 8 hours  

And here's the listing that produced that output. There's a lot of little (and some big) differences between Rust source code and equivalent C++. For example, there's very little up-front boilerplate code. Note the lack of any #include statements or any using statements to shorten the use of C++ namespace prefixes. Note also how functions are defined, and how the return types are defined and returned from each function. There is no explicit return statement. If you've defined a return type, then the last statement in a Rust function is an implied value return, without the return statement or even a semicolon at the end of the statement.

  extern crate fs4;    use std::fs::File;  use std::io::BufReader;  use std::io::prelude::*;    fn translate_uptime(line: String) -> String {      let uptime_seconds_str = line.split(' ').collect::<Vec<_>>()[0].trim();      let mut uptime_in_seconds:f64 = uptime_seconds_str.parse::<f64>().unwrap();      let seconds_in_a_day = 86400f64;      let seconds_in_an_hour = 3600f64;      let days_uptime = (uptime_in_seconds / seconds_in_a_day).floor();      uptime_in_seconds -= days_uptime * seconds_in_a_day;      let hours_uptime = (uptime_in_seconds / seconds_in_an_hour).round();        if days_uptime > 0f64 {          format!("{days_uptime:} days, {hours_uptime:} hours")      }      else {          format!("{hours_uptime:} hours")      }  }    fn translate_meminfo(line: &str) -> f64 {      let substring = line.split(':').collect::<Vec<_>>()[1].trim();      let number = substring.split(' ').collect::<Vec<_>>()[0].trim();      let value: f64 = number.parse::<f64>().unwrap();      value / (1024.0f64 * 1024.0f64)  }    fn decode_cpu_part_number(line: &str) -> &str {      let part_number = line.split(':').collect::<Vec<_>>()[1].trim();        // Partial matching of ARMv8-A part numbers.      // See https://en.wikipedia.org/wiki/Comparison_of_ARM_processors#ARMv8-A      //      match part_number.to_uppercase().as_str() {          "0XD01" => "Cortex-A32",          "0XD02" => "Cortex-A34",          "0XD03" => "Cortex-A53",          "0XD04" => "Cortex-A35",          "0XD05" => "Cortex-A55",          "0XD06" => "Cortex-A65",          "0XD07" => "Cortex-A57",          "0XD08" => "Cortex-A72",          "0XD09" => "Cortex-A73",          "0XD0A" => "Cortex-A75",          "0XD0B" => "Cortex-A76",          "0XD0D" => "Cortex-A77",          "0XD0E" => "Cortex-A76AE",          _ => "NO MATCH",      }  }    fn get_filesystem_metrics() -> (f64, f64) {      let mut total_space = fs4::total_space("/").unwrap() as f64;      let mut available_space = fs4::available_space("/").unwrap() as f64;      let devisor = 1024f64 * 1024f64 * 1024f64;      total_space = total_space / devisor;      available_space = available_space / devisor;      (total_space, available_space)  }    fn main() -> std::io::Result<()> {      let cpuinfo_file = File::open("/proc/cpuinfo")?;      let mut buf_reader = BufReader::new(cpuinfo_file);      let mut cpuinfo = String::new();      buf_reader.read_to_string(&mut cpuinfo);        let mut core_count = 0;      let mut cpu_part = "";      let mut model = "";        for line in cpuinfo.lines() {          if line.contains("CPU part") && cpu_part.is_empty() {              cpu_part = decode_cpu_part_number(line);          }          if line.contains("processor") { core_count += 1; }          if line.contains("Model") && model.is_empty() {              model = line.split(':').collect::<Vec<_>>()[1].trim();          }      }        let meminfo_file = File::open("/proc/meminfo")?;      buf_reader = BufReader::new(meminfo_file);      let mut meminfo = String::new();      buf_reader.read_to_string(&mut meminfo);        let mut memory_total = 0.0f64;      let mut memory_free = 0.0f64;      let mut swap_total = 0.0f64;      let mut swap_free = 0.0f64;        for line in meminfo.lines() {          if line.contains("MemTotal:") {memory_total = translate_meminfo(line);}          if line.contains("MemAvailable:") {memory_free = translate_meminfo(line);}          if line.contains("SwapTotal:") {swap_total = translate_meminfo(line);}          if line.contains("SwapFree:") {swap_free = translate_meminfo(line);}      }        let uptime_file = File::open("/proc/uptime")?;      buf_reader = BufReader::new(uptime_file);      let mut uptime = String::new();      buf_reader.read_to_string(&mut uptime);      let system_uptime = translate_uptime(uptime);        let version_file = File::open("/proc/version")?;      buf_reader = BufReader::new(version_file);      let mut version = String::new();      buf_reader.read_to_string(&mut version);      let kernel_version = version.split(' ').collect::<Vec<_>>()[2].trim();        let os_release_file = File::open("/etc/os-release")?;      buf_reader = BufReader::new(os_release_file);      let mut os_release = String::new();      buf_reader.read_to_string(&mut os_release);      let mut pretty_name = "";        for line in os_release.lines() {          if line.contains("PRETTY_NAME=") {              let pretty_name_quoted = line.split('=').collect::<Vec<_>>()[1];              pretty_name = &pretty_name_quoted[1..pretty_name_quoted.len()-1];          }      }        let (total_space, available_space) = get_filesystem_metrics();        println!(" Raspberry Pi model:   {}", model);      println!(" Core model / count:   {} / {}", cpu_part, core_count);      println!(" Kernel version:       {}", kernel_version);      println!(" Operating system:     {}", pretty_name);      println!(" Memory total / free:  {:.2} Gi / {:.2} Gi", memory_total, memory_free);      println!(" Swap total / free:    {:.2} Gi / {:.2} Gi", swap_total, swap_free);      println!(" Filesystem capacity:  {:.1} Gi", total_space);      println!(" Filesystem available: {:.1} Gi", available_space);      println!(" Uptime:               {}", system_uptime);      Ok(())  }  

One of my favorite Rust discoveries has been the match expression in the function decode_cpu_part_number starting on line 31. There is no switch in Rust, only match, and from what I've been able to ascertain match is far superior. My biggest complaint about the C++ switch statement is that it will only work with native types, such as int. Match will work with anything. In the function decode_cpu_part_number starting on line 37 I lay out clearly and cleanly the match between a given CPU value and what CPU type that value maps to, without having to go through the extra step of creating an explicit map. Rust allows me to implement a clean and easily readable decoder. I say easily readable because I have no doubt that sometime in the future I might have to go back in and add something, so it's nice there is no ambiguity in that function.

Another Rust code feature I appreciate is println! and format!. They behave an awful lot like how Python's print and string formatting features work. Once again C++ came up with std::cout and all its supporting functions, and while I learned it well enough to do sophisticated work, it would produce a long line of code I just didn't care for. And it goes on.

At a total of 138 lines, I don't consider the total source code length to be too long. It's more than reasonable. I'd also like to point out that I know this is very specific to Linux, and in particular Linux on the Raspberry Pi 5, and at this point I don't really care. I'm working to make it operate correctly in this environment rather than think about optimizations or portability to other systems. To quote Donald Knuth:

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

Or as a wizened software engineer once told me decades ago, make it right before you make it fancy.

And before I forget, here's the Cargo.toml file for the source:

  [package]  name = "pistats"  version = "0.5.0"  edition = "2021"    # See more keys and their definitions at  # https://doc.rust-lang.org/cargo/reference/manifest.html    [lints.rust]  #non_snake_case = "allow"  #unused_imports = "allow"  unused_must_use = "allow"  #unused_variables = "allow"    [dependencies]  fs4 = "0.9.1"  

The toml file sets up the build environment. I also like the linting section, and I use that to turn off build warnings. The lint settings are counter-intuitive. For example, the one uncommented line (line 12) is telling rustc (the Rust compiler) to not emit any lines of code that isn't doing anything with the return types from function calls. For example, I'm ignoring all returns from buf_reader.read_to_string(...) (source lines 68, 87, 194, 110, and 116). If that line were commented out, then I'd get five very verbose warnings about my ignoring those returns when I built the application with cargo. There are a lot more lint controls, which I strongly recommend you read about.

Links

Rust -- https://en.wikipedia.org/wiki/Rust_(programming_language)

Go -- https://en.wikipedia.org/wiki/Go_(programming_language)

Ultrix -- https://en.wikipedia.org/wiki/Ultrix

Cfront -- https://en.wikipedia.org/wiki/Cfront

Comment
Like
You can also reply to this email to leave a comment.

Arcane Science Lab © 2024.
Manage your email settings or unsubscribe.

WordPress.com and Jetpack Logos

Get the Jetpack app

Subscribe, bookmark, and get real‑time notifications - all from one app!

Download Jetpack on Google Play Download Jetpack from the App Store
WordPress.com Logo and Wordmark title=

Automattic, Inc.
60 29th St. #343, San Francisco, CA 94110

at September 06, 2024
Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest

No comments:

Post a Comment

Newer Post Older Post Home
Subscribe to: Post Comments (Atom)

NEW! Weekly Menu Inspo ⭐️

Simple, nourishing recipes to add to your menu this week: a protein-rich breakfast, veggie soup, vibrant bowl meal...

  • [New post] This tried-and-true Irish pub-inspired soup is creamy and thick with chunks of potatoes and leeks throughout.
    Emily Morgan posted: "This tried-and-true Irish pub-inspired soup is creamy and thick with chunks of potatoes and leeks thr...
  • LA COURONNE LYONNAISE, TWO WAYS
    This bread originates in Lyon, and is shaped as a crown, therefore the name ...
  • Keto Chicken Pot Pie Casserole (Gluten-Free)
    INGREDIENTS US CustomaryMetric▢4 cups cooked chicken breast (roasted, rotisserie...

Search This Blog

  • Home

About Me

phoo, ramen, soba
View my complete profile

Report Abuse

Blog Archive

  • December 2025 (16)
  • November 2025 (18)
  • October 2025 (21)
  • September 2025 (19)
  • August 2025 (28)
  • July 2025 (25)
  • June 2025 (28)
  • May 2025 (34)
  • April 2025 (36)
  • March 2025 (39)
  • February 2025 (36)
  • January 2025 (43)
  • December 2024 (46)
  • November 2024 (51)
  • October 2024 (44)
  • September 2024 (1172)
  • August 2024 (1572)
  • July 2024 (1413)
  • June 2024 (1289)
  • May 2024 (1362)
  • April 2024 (1472)
  • March 2024 (1827)
  • February 2024 (2413)
  • January 2024 (2936)
  • December 2023 (2135)
  • November 2023 (1639)
  • October 2023 (1285)
  • September 2023 (918)
  • August 2023 (864)
  • July 2023 (795)
  • June 2023 (800)
  • May 2023 (796)
  • April 2023 (754)
  • March 2023 (649)
  • February 2023 (736)
  • January 2023 (1159)
  • December 2022 (968)
  • November 2022 (921)
  • October 2022 (852)
  • September 2022 (708)
  • August 2022 (766)
  • July 2022 (877)
  • June 2022 (684)
  • May 2022 (716)
  • April 2022 (698)
  • March 2022 (781)
  • February 2022 (734)
  • January 2022 (955)
  • December 2021 (1387)
  • November 2021 (3002)
  • October 2021 (3213)
  • September 2021 (3188)
  • August 2021 (3232)
  • July 2021 (1697)
Powered by Blogger.