beginner – Rust Book’s Chapter 8 – Text Interface

I recently finished chapter 8 of Rust’s book, and below is my solution to the third exercise. I’d appreciate pointers on how the code can be improved. Thanks in advance.

use std::collections::HashMap;

//An attempt at Rust book's chapter 8's third exercise:
//https://doc.rust-lang.org/book/ch08-03-hash-maps.html

fn main() {
    println!("The Office - Text Interface.");
    println!();
    println!("Enter a query, type HELP for a list of keyword and their functions, or type EXIT to exit.");
    println!();

    //build hashmap{department: vec(names)} database, insert default values
    let mut company = HashMap::new();
    
    let depts = vec!("SALES", "ENGINEERING", "HR", "SANITATION");

    let sales = vec!("Sally", "Jordan", "Charlie", "Abigail");
    let engineering = vec!("Suzy", "Jay", "Chi", "Amy");
    let hr = vec!("Son", "Jack", "Chia", "Anna");
    let sanitation = vec!("August", "Entangle", "Will", "Jada");
    let tup = (sales, engineering, hr, sanitation);

    let mut g: Vec<_> = Vec::new();
    company = depts.into_iter()
    .map(|x| x.to_string())
    .zip(tup.iter().map(|y| {g = y.iter().map(|q| q.to_string()).collect(); g.clone()}))
    .collect();

    let keywords = ("ADD", "LIST", "UPDATE", "REMOVE", "HELP", "EXIT");

    // loop the input part of the text interface.
    //validate first keyword, send queries to functions.
    loop {
        let mut query = String::new();
        println!("::");

        //check for empty input
        io::stdin().read_line(&mut query).expect("Enter a valid input");
        query = query.trim().to_string();
        // println!("{}", query);
        if query.is_empty() {
            println!("Invalid input. Type HELP for a keyword reference.");
            continue;
        }

        //check for valid first keyword
        let keyword = query.split_whitespace().next().unwrap().to_uppercase();
        if !keywords.contains(&&keyword(..)) {
            println!("Invalid Keyword. Type HELP for a keyword reference.");
            continue;
        }

        //keyword validated. Call the function.
        let mut query = query.split_whitespace().collect::<Vec<_>>();
        match &&keyword(..) {
            &"EXIT" => return,
            &"HELP" => help(),
            &"ADD" => add(&mut query, &mut company),
            &"LIST" => list(&mut query, &mut company),
            &"UPDATE" => update(&mut query, &mut company),
            &"REMOVE" => remove(&mut query, &mut company),
            _ => (),
        }
        // println!("{:?}", company); //debug purposes: print the entire hashmap on each loop to monitor changes.
        continue;
    }
}

fn add(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {

    //validate add syntax
    let length = q.len();
    if length < 3 || length > 4 {
        println!("Invalid ADD syntax. Type HELP for a keyword reference.");
        return;
    }

    //add a new department
    if length == 3 {
        match (q(0), q(1), q(2)) {
            ("ADD", "-D", d) => {

                //check if dept exists
                let dept = d.to_uppercase();
                if company.contains_key(&dept) {
                    println!("Department {} already exists.", d);
                    return;
                }

                //add dept
                company.entry(dept).or_insert(Vec::new());
                println!("Created department {}.", d);
                return;

            }

            _ => {
                println!("Invalid syntax.");
                return;
            }
        }
    }

    //add a person to a department
    if length == 4 {
        match (q(0), q(1), q(2), q(3)) {
            ("ADD", name, "TO", d) => {

                //check if dept exists
                let dept = d.to_uppercase();
                if !company.contains_key(&dept) {
                    println!("Department {} does not exist.", d);
                    return;
                }

                //check if name already exists in dept
                if company(&dept).contains(&name.to_owned()) {
                    println!("The name {} already exists in {}.", name, dept);
                    return;
                }
                //add name to vector
                (*company.get_mut(&dept).unwrap()).push(name.to_owned());
                println!("Added {} to {}.", name, d);
            }
            _ => {
                println!("Invalid Syntax");
                return;
            }
        }
    }
}

fn list(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {

    //sanitize input
    let length = q.len();
    if length != 2 && length !=4 {
        println!("Invalid number of arguments.");
        return;
    }

    if length == 2 {
        match (q(0), q(1)) {

            //list all depts
            ("LIST", "-D") => {
                let mut depts: Vec<_> = company.keys().collect();
                depts.sort();
                for d in depts {
                    println!("{}", d);
                }
                return;
            }

            //list everyone in all depts, sorted alphabetically
            ("LIST", "-E") => {
                for (dept, mut names) in company.clone() {
                    println!("---{}---", dept);
                    names.sort();
                    for name in names {
                        println!("{}", name);
                    }
                }
            }
            _ => {
                println!("Invalid Syntax.");
                return;
            }
        }
    }

    if length == 4 {
        match (q(0), q(1), q(2), q(3)) {
            ("LIST", "-E", "IN", d) => {

                //check if dept exists
                let dept = d.to_uppercase();
                if !company.contains_key(&dept) {
                    println!("Department {} does not exist.", d);
                    return;
                }

                //list all in department
                println!("---{}---", dept);
                (*company.get_mut(&dept).unwrap()).sort();
                for name in &company(&dept) {
                    println!("{}", name);
                }
            }
            _ => {
                println!("Invalid Syntax.");
                return;
            }
        }
    }
}

fn update(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
    let length = q.len();

    if length != 5 && length != 6 {
        println!("Invalid UPDATE syntax.");
        return;
    }

    if length == 5 {
        match (q(0), q(1), q(2), q(3), q(4)) {

            //update a department
            ("UPDATE", "-D", old_d, "TO", new_d) => {

                //check if dept exists
                let old_dept = old_d.to_uppercase();
                let new_dept = new_d.to_uppercase();
                if !company.contains_key(&old_dept) {
                    println!("Department {} does not exist.", old_d);
                    return;
                }
                if company.contains_key(&new_dept) {
                    println!("Department {} already exists.", new_d);
                    return;
                }

                //rename dept. Technique is to build a new vector with that same name since you
                //cannot change the key of a hash map.
                let temp_dept = company.get(&old_dept).unwrap().clone();
                company.insert(new_dept.to_uppercase(), temp_dept);
                company.remove(&old_dept);
                println!("Changed Department {} to {}.", old_d, new_d);
                return;

            }
            _ => {
                println!("Invalid syntax.");
                return;
            }
        }
    }

    //change a name in a department
    match (q(0), q(1), q(2), q(3), q(4), q(5)) {
        ("UPDATE", old_name, "FROM", d, "TO", new_name) => {

            //check if dept exists
            let dept = d.to_uppercase();
            if !company.contains_key(&dept) {
                println!("Department {} does not exist.", d);
                return;
            }

            //check if old name and new name exist
            if !company(&dept).contains(&old_name.to_owned()) {
                println!("The name {} does not exist in {}.", old_name, dept);
                return;
            }
            if company(&dept).contains(&new_name.to_owned()) {
                println!("The name {} already exists in {}.", new_name, dept);
                return;
            }

            //update the name.
            for (i, name) in company(&dept).clone().iter().enumerate() {
                if name == old_name {
                    (*company.get_mut(&dept).unwrap())(i) = new_name.to_owned();
                    println!("Changed {} in {} to {}.", old_name, dept, new_name);
                    return;
                }
            }
        }
        _ => {
            println!("Invalid Syntax.");
            return;
        }
    }
}

fn remove(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
    let length = q.len();

    if length !=3 && length !=4 {
        println!("Invalid REMOVE syntax.");
        return;
    }

    if length == 3 {
        match (q(0), q(1), q(2)) {
            ("REMOVE", "-D", d) => {

                //check if dept exists
                let dept = d.to_uppercase();
                if !company.contains_key(&dept) {
                    println!("Department {} does not exist.", d);
                    return;
                }

                //remove the department.
                company.remove(&dept);
                println!("Removed department {}.", d);
                return;

            }
            _ => {
                println!("Invalid Syntax.");
                return;
            }
        }
    }

    //remove a person
    match (q(0), q(1), q(2), q(3)) {
        ("REMOVE", name, "FROM", d) => {

            //check if dept exists
            let dept = d.to_uppercase();
            if !company.contains_key(&dept) {
                println!("Department {} does not exist.", d);
                return;
            }

            //check if name exists
            if !company(&dept).contains(&name.to_owned()) {
                println!("The name {} does not exist in {}.", name, dept);
                return;
            }

            //remove the name
            for (i, _name) in company(&dept).clone().iter().enumerate() {
                if _name == name {
                    (*company.get_mut(&dept).unwrap()).remove(i);
                    println!("Removed {} from {}.", name, dept);
                    return;
                }
            }

        }
        _ => {
            println!("Invalid Syntax.");
            return;
        }
    }
}

fn help() {
    println!("The Office - KEYWORD HELP");
    println!();
    println!("Note: All keywords are case-sensitive.");
    println!("Keywords: nLIST - Lists items in the database");
    println!("Usage:    LIST -E - Lists all employees");
    println!("          LIST -E IN (DEPARTMENT) - Lists all employees in specified department.");
    println!("          LIST -D - Lists all departmnets in the company");
    println!();
    println!("ADD -     Adds items to the database.");
    println!("Usage:    ADD (name) TO (department) - Adds the name to the specified department.");
    println!("          ADD -D (department) - Adds the department to the roster.");
    println!();
    println!("REMOVE -  Removes items from the database.");
    println!("          REMOVE -D (department) - Removes the particular department from the database.");
    println!("          REMOVE (name) FROM (department) - Removes the person from the specified department.");
    println!();
    println!("UPDATE -  Changes records in the database.");
    println!("Usage:    UPDATE -D (old name) TO (new name) - Changes a department's name.");
    println!("          UPDATE (old name) FROM (department) TO (new name) - Changes a person's name.");
    println!();
    println!("HELP -    Prints this help screen.");
    println!();
    println!("EXIT -    Exits the program.")
}

rust – How to write a slice into a file?

I have code which interracts with C via bindings. In it I have this:

let a1: *const i8 = get_data();
let size1 = get_size_of_data();

let a2 = ::std::slice::from_raw_parts(a2, size1);

I need to write a2 or a1 into a binary file. The size or length of the data is known.

I tried:

  let mut f1 = File::create("my_file.data").unwrap();
  // ??? f1.write_all(a2).expect("unable to write binary data to file");
  // ??? f1.write_all(a2.as_bytes).expect("unable to write binary data to file");

and nothing has compiled.

How to do it?

rust – I could use some help refactoring my code so I can become better

Good evening, I’m new to Rust and would like some help refactoring this small app I made so that it’s more efficient, even if it may be unnecessary.

Basically I’m trying to figure out better ways to do stuff, and if I could analyze how someone would do the same thing, but better, I think it could help me learn a bit more about the language.

I haven’t got too far into the language yet, but I would like to nip any bad habits I might run into before they become a problem.

What this app basically does, is it asks for how many cards are in a Magic: The Gathering deck, and then a few questions about the cards, then determines how much and what types of lands you should have in the deck.

use std::io;

fn main() {
    let deck_size = get_deck_size();
    let spells = get_spell_count(deck_size);
    let total_cmc = get_total_cmc();

    calculate_mana(deck_size, spells, total_cmc);

    fn get_deck_size() -> f32 {
        loop {
            let mut input = String::new();
            println!("How many cards are in your deck?");
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read input.");
            let input = input
                .trim()
                .parse::<f32>()
                .expect("Please enter a valid number.");

            if input >= 40.0 {
                break input;
            }

            println!("Your deck is too small, the minimum amount of cards in a deck is 40, please enter a new deck size.")
        }
    }

    fn get_spell_count(deck_size: f32) -> f32 {
        loop {
            let mut input = String::new();
            println!("How many spells are in your deck?");
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read input.");
            let input = input
                .trim()
                .parse::<f32>()
                .expect("Please enter a valid number.");

            if input <= deck_size {
                break input;
            }

            println!("You have more spells in your deck than the amount of cards in your deck, please enter a new number of spells.")
        }
    }

    fn get_total_cmc() -> f32 {
        loop {
            let mut input = String::new();
            println!("What's the total converted mana cost of all your spells?");
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read input.");
            let input = input
                .trim()
                .parse::<f32>()
                .expect("Please enter a valid number.");

            if input >= 0.0 {
                break input;
            }

            println!("Something is wrong here.")
        }
    }

    fn calculate_mana(deck_size: f32, spells: f32, total_cmc: f32) {
        let mut symbol_count = Vec::new();
        let total_lands = deck_size - spells;
        let colors = ("white", "blue", "green", "red", "black", "colorless");

        println!("Now we need to get all the mana symbols throughout your deck (not just in the cmc, but in the cards abilities as well).");

        for i in colors.iter() {
            let mut input = String::new();
            println!("How many {} symbols are in the deck?", i);
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read input.");
            let color = (
                input
                    .trim()
                    .parse::<f32>()
                    .expect("Please enter a valid number."),
                i,
            );
            symbol_count.push(color);
        }

        println!("Your average CMC is: {}", total_cmc / deck_size);
        println!("You should have {} total land", total_lands);

        for i in symbol_count {
            if i.0 > 0.0 {
                let x = ((total_lands / spells) * i.0).round();
                println!("{} of those lands should be {}", x, i.1);
            }
        }
    }
}

rust – Pattern matches Result either into T or new error E’

this question is about pattern matching and wrapping Error types in Rust.

I love the Result type in Rust and I often find myself doing things like the below code snippet. This type of match is particularly relevant when I want to wrap an error from a library (e.g. std::io::Error) with my own error type.

use std::io::File;

enum Error {
    IOError(std::io::ErrorKind),
}

fn open_file(path: &path) -> Result<File, Error> {
    let file = match File::open(path) {
        Ok(file) => file,
        Err(err) => Err(Error::IOError(err.kind())?,
    };

    // I might use `file` here before moving it out of the function.

    Ok(file)
}

This example uses file opening as an example but I use this pattern a lot, particularly when wrapping a specific library (e.g. std::io) with another library that uses it.


My specific questions are to do with the ? syntax in place of return (or similar). In general, I tend to avoid return as it is not how I ‘think in Rust’.

  1. Is this considered idiomatic rust?
  2. If not, is there an alternative that doesn’t use return Err(Error::IOError(err.kind())?
  3. Should I just shut up and use return?

Rust server load testing – Game Development Stack Exchange

I am looking to stress test my Rust game server and monitor CPU, RAM and FPS. I could not find much around.

I general I think I will need a good amount of compute power, bunch of cloud based VM which generate the load through UDP using some headless client.

Monitoring CPU and RAM should be fairly easy, but how about the in game FPS?
Also I could not find much about Rust clients that could simulate a player.

Thanks

How to store an invariant type variable in Rust

I would like to parse the type of each value in a row of data from tokio-postgresql

Here is an example of getting a single value for a row of data from postgresql:

...
let rows = client
   .query("select * from ExampleTable;")
   .await?;

// And then check that we got back the same string we sent over.
let thisValue: &str = rows(0).get(0);

In this example, it is known at design-time that the type in the first column is a string, and therefore the type for thisValue is &str. I would like to accept an invariant type.

I intend to use std::any::type_name::<T>() to derive the type name in thisValue and then use conditional logic (if/switch) to process this data differently depending on the type.

Is there an invariant way to store a variable in Rust? Will std::any::type_name::<T>() work on that variable? Is there another way to “box” the variable instead?

I understand that std::any::type_name::<T>() is using a kind of generics interface. To me, this means it’s probably a compile-time strategy, not run-time. So I have my doubts that the way I am researching will work, but I hope I am on the right track and only need the final piece: an invariant type.

coding style – Is this enum/trait a good way to implement polymorphic design in Rust?

this is my first post on here, and I’m wondering about a good rust implementation with traits on enum types. I want to know if using an enum w/ a trait as a generator of different code is viable like the one below? Also a couple of things I’m curious about is if there is an overhead with the match statement? Would implementing separate structs be faster?

struct GeneralCell {
    repr: char,
    position: Position,
    cell_type: Cell,
}

trait InitializeCells {
    fn new(&self, position: Position) -> GeneralCell;
}

pub enum Cell {
    Hall,
    Filled,
    Empty,
}

impl InitializeCells for Cell {
    fn new(&self, position: Position) -> GeneralCell {
        match self {
            Cell::Hall => GeneralCell {
                repr: 'H',
                position,
                cell_type: Cell::Hall,
            },
            Cell::Empty => GeneralCell {
                repr: 'E',
                position,
                cell_type: Cell::Empty,
            },
            Cell::Filled => GeneralCell {
                repr: 'F',
                position,
                cell_type: Cell::Filled,
            },
        }
    }
}

Pig Latin Application in rust

Just finished an exercise in the Rust online book and I wanted to know if there is anything worth talking about in the code I wrote… if there’s any mistake or optimization possible.

// The first consonant of each word is moved to the end of the word and “ay” is added, so “first” becomes “irst-fay”.
// Words that start with a vowel have “hay” added to the end instead (“apple” becomes “apple-hay”).
// Keep in mind the details about UTF-8 encoding!)
use std::io;
fn main() {
    let mut user_input = String::new();
    io::stdin()
        .read_line(&mut user_input)
        .expect("Failed to read line.");
    for word in user_input.split_whitespace() {
        match word.chars().nth(0).unwrap() {
            'a' | 'e' | 'i' | 'o' | 'u' | 'y' => print!("{} ", format!("{}-hay", word.trim())),
            _ => print!("{} ", format!("{}{}-ay", &word(word.chars().next().unwrap().len_utf8()..).trim(), word.chars().nth(0).unwrap())),
        };
    }
    println!();
}

Simple-ish Rust implementation of ‘cat’

I’ve seen some other implementations of cat (even in Rust) on this site, but none attempted to completely implement the GNU cat CLI as far as I can tell. This version that I am developing is complete (minus the -u parameter that is ignored by GNU cat anyway) and, as far as I can tell, works.

Some problems:

  • It requires proper Unicode unless -A, -v, or -e are passed.
  • The code is a bit of a mess and could use cleaning up. Mainly this involves the way I have divided it into functions as well as my implementation of error handling.
  • I repeat myself a couple of times here and there.
  • It’s all in one file. (This one is easily fixed.)

After all, the reason I am doing this is to learn. Could someone show me where my code could be best improved? Be as pedantic as you’d like. Thanks in advance.

(Anyway, here’s the code.)

use common::Arg;
use std::default::Default;
use std::env;
use std::fmt::Debug;
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::io::BufReader;

fn show_help(program_name: &str) {
    print!("Usage: {0} (options) <file>...
Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
  Options:
    -A, --show-all          Equivalent to -vET
    -b, --number-nonblank   Number all non-empty output lines, starting with 1
    -e                      Equivalent to -vE
    -E, --show-ends         Display a '$' after the end of each line
    -n, --number            Number all output lines, starting with 1; ignored if -b was passed
    -s, --squeeze-blank     Suppress repeated adjacent blank lines, output one empty line instead of many
    -t                      Equivalent to -vT
    -T, --show-tabs         Display TAB characters as '^I'
    -v, --show-nonprinting  Display control characters (except for LFD and TAB) using '^' notation and precede characters that have the high bit set with 'M-'
  {0} normally reads and writes in binary mode, unless one of -bensAE is passed or standard output is a terminal. An exit status of zero indicates success, and a nonzero value indicates failure.
  ", program_name);
}

#(derive(Debug))
struct Configuration {
    show_nonprinting: bool,
    show_tabs: bool,
    squeeze_blanks: bool,
    number_lines: bool,
    show_line_ends: bool,
    number_nonblanks: bool,
}

impl Default for Configuration {
    fn default() -> Self {
        Configuration {
            show_nonprinting: false,
            show_tabs: false,
            show_line_ends: false,
            squeeze_blanks: false,
            number_lines: false,
            number_nonblanks: false,
        }
    }
}

fn main() -> Result<(), ()> {
    let raw_args: Vec<String> = env::args().collect();
    let args = common::parse_args(&raw_args(1..));

    let mut config = Configuration::default();
    let mut files: Vec<String> = vec!();

    for arg in args {
        match arg {
            Arg::Flag(text) => match text.as_str() {
                "h" | "help" => {
                    show_help(&raw_args(0));
                    return Ok(());
                }
                "A" | "show-all" => {
                    config.show_nonprinting = true;
                    config.show_line_ends = true;
                    config.show_tabs = true;
                }
                "b" | "number-nonblank" => {
                    config.number_nonblanks = true;
                }
                "e" => {
                    config.show_nonprinting = true;
                    config.show_line_ends = true;
                }
                "E" | "show-ends" => {
                    config.show_line_ends = true;
                }
                "n" | "number" => {
                    if !config.number_nonblanks {
                        config.number_lines = true;
                    }
                }
                "s" | "squeeze-blank" => {
                    config.squeeze_blanks = true;
                }
                "t" => {
                    config.show_nonprinting = true;
                    config.show_tabs = true;
                }
                "T" | "show-tabs" => {
                    config.show_tabs = true;
                }
                "v" | "show-nonprinting" => {
                    config.show_nonprinting = true;
                }
                _ => {
                    show_help(&raw_args(0));
                    return Err(());
                }
            },
            Arg::Positional(text) => {
                files.push(text);
            }
            Arg::FlagEquals(_, _) => {
                // no x=y flags in cat
                show_help(&raw_args(0));
                return Err(());
            }
        }
    }
    if files.len() == 0 {
        files.push("-".to_owned());
    }

    let config = config;
    let files = files;

    for file in files {
        if file == "-" {
            let stdin_handle = io::stdin();
            let stdin_reader = BufReader::new(stdin_handle);
            if let Err(e) = cat(stdin_reader, &config) {
                eprintln!("ERROR: reading stdin: {}", e);
                return Err(());
            }
        } else {
            let file_handle = File::open(&file).map_err(|e| {
                eprintln!("ERROR: opening {}: {}", file, e);
                ()
            })?;
            let file_reader = BufReader::new(file_handle);
            if let Err(e) = cat(file_reader, &config) {
                eprintln!("ERROR: reading {}: {}", file, e);
                return Err(());
            }
        }
    }

    Ok(())
}

fn cat<F>(mut reader: BufReader<F>, config: &Configuration) -> io::Result<()>
where
    F: io::Read,
{
    let mut line = 1u64;
    let mut prev_line_was_blank = false;
    // builds a UTF-8 char when necessary
    let mut utf8_char: Vec<u8> = vec!();
    let mut utf8_bytes_rem = 0;
    loop {
        let buf = reader.fill_buf()?;
        let len = buf.len();
        if len == 0 {
            break;
        }
        let mut line_buf = String::new();
        for c in buf {
            let mut cs = if config.show_nonprinting {
                print_char_escaped(*c)
            } else {
                String::new()
            };
            if config.show_tabs && (*c == 9 || *c == 0b10001001) {
                if *c & 0b10000000 != 0 {
                    cs = String::from("M-");
                }
                cs += &format!("^I");
            }
            if cs.is_empty() {
                // non-special
                if *c == b'n' {
                    let current_line_is_blank =
                        (config.number_nonblanks || config.squeeze_blanks) && line_buf.is_empty();
                    if config.show_line_ends {
                        line_buf.push('$');
                    }
                    if !(config.squeeze_blanks && current_line_is_blank && prev_line_was_blank) {
                        // line should be printed
                        if config.number_lines
                            || (config.number_nonblanks && !current_line_is_blank)
                        {
                            // number line
                            print!("{:6}  ", line);
                            line += 1;
                        }
                        // print line
                        println!("{}", line_buf);
                        line_buf = String::new();
                    }
                    prev_line_was_blank = current_line_is_blank;
                } else {
                    if utf8_bytes_rem == 0 && *c & 0xc0 != 0xc0 {
                        line_buf.push(*c as char);
                    } else if *c & 0xc0 == 0xc0 {
                        // start of Unicode char
                        let mut mask = 0x20;
                        utf8_bytes_rem = 1;
                        while *c & mask != 0 {
                            if mask == 0x02 {
                                // bad Unicode!
                                return Err(io::Error::new(
                                    io::ErrorKind::InvalidData,
                                    "Invalid UTF-8",
                                ));
                            }
                            utf8_bytes_rem += 1;
                            mask >>= 1;
                        }
                        utf8_char.push(*c);
                    } else {
                        // continued Unicode char
                        if *c & 0xc0 != 0x80 {
                            // bad continuation!
                            return Err(io::Error::new(
                                io::ErrorKind::InvalidData,
                                "Invalid UTF-8",
                            ));
                        }
                        utf8_bytes_rem -= 1;
                        utf8_char.push(*c);
                        if utf8_bytes_rem == 0 {
                            // should not panic; validity was checked!
                            let uc = std::str::from_utf8(&utf8_char).unwrap();
                            line_buf.push_str(uc);
                        }
                        utf8_char = vec!();
                    }
                }
            } else {
                line_buf.push_str(&format!("{}", cs));
            }
        }
        reader.consume(len);
    }
    Ok(())
}

fn print_char_escaped(c: u8) -> String {
    let mut c = c;
    // do non-printing checks
    let (meta, high_bit) = if c & 0b10000000 != 0 {
        // high bit set, unset
        c &= !0b10000000;
        (format!("M-"), true)
    } else {
        (String::new(), false)
    };
    let c = c;
    // now check for control chars
    let cs = match c {
        // null char
        0 => format!("^@"),
        // control char is represented as letter
        1..=26 => {
            if high_bit {
                format!("^{}", (c + 64) as char)
            } else {
                match c {
                    // TAB, ignore; it is handled by config.show_tabs
                    9 => String::new(),
                    // LF, ignore; it is handled by config.show_line_ends
                    10 => String::new(),
                    // other control chars are treated the same
                    _ => format!("^{}", (c + 64) as char),
                }
            }
        }
        // ESC
        27 => format!("^("),
        // FS
        28 => format!("^\"),
        // GS
        29 => format!("^)"),
        // RS
        30 => format!("^^"),
        // US
        31 => format!("^_"),
        // DEL
        127 => format!("^?"),
        // printable char
        _ => format!("{}", c as char),
    };
    meta + cs.as_str()
}

common is my own crate which implements the basic argument parsing used here. I can supply it if someone wants it.