Skip to content

Some notes #1

@atimos

Description

@atimos

It looks good, and it works!
I have some notes to write more idiomatic rust.
Just reach out if you have more questions!

instead of using &Vec<something> and &String as parameters you can use
&[something] and &str. its not super important but its more efficient and more rusty.
https://users.rust-lang.org/t/there-is-any-performance-difference-between-vec-f32-and-f32/72707/3
that answer applies to String/&str as well.

and you could write the shorten function like

pub fn shorten(text: &str, len: usize) -> &str {
    if text.len() < len { text } else { &text[..len] }
}

to not allocate a new string.
its not important for your program, but its an optimization :)
and to be extra fancy you can write it as

pub fn shorten(text: &str, len: usize) -> &str {
    &text[..text.len().min(len)]
}

it may not be as readable for everyone, but just to show that rust doesn't always have to be verbose :)

you don't need the execute crate, you can use std to do the same thing

pub fn run_command(cmd: &str, args: &[&str]) -> String {
    String::from_utf8(Command::new(cmd).args(args).output().unwrap().stdout).unwrap()
}

and if you return a Result<String, Error> from the run_command command you can use question marks to propagate errors.
you can specify your Error struct/enum if you want to do fancy things, but you can just use the crate anyhow and return anyhow::Result<String> to make it easy for you

pub fn run_command(cmd: &str, args: &[&str]) -> anyhow::Result<String> {
    Ok(String::from_utf8(Command::new(cmd).args(args).output()?.stdout)?)
}

anyhow::Result<String> is just a short hand for Result<String, anyhow::Error>

If you change your main function to have the signature

fn main() -> anyhow::Result<()>

and end the main function with

Ok(())

you can use question mark in main as well, so you can write

let output = run_command("ps", &["--no-headers", "aexo", "pid,args"])?;

this is not super important for this program but its one of the core things in writing rust, how to handle and propagate errors.
Its generally not a great idea to use unwrap because that will crash the whole program if it cant unwrap
https://doc.rust-lang.org/book/ch09-00-error-handling.html
read section 9.1, 9.2 and 9.3 for a more rusty way of error handling

now for some opinions...
the first loop in main, to get findings can be written like

    let findings = output
        .lines()
        .filter_map(|line| line.trim_start().split_once(' '))
        .filter(|(_, name)| name.contains(&args.name))
        .map(|(pid, name)| Ok(ProcessLine { name: name.into(), pid: pid.parse()? }))
        .collect::<anyhow::Result<Vec<_>>>()?;

i think that is a bit more in style of ruby, so i guess its easier for you to follow.
the collect method is magic, its one of the best methods that exists, it can collect to basically anything.
in the example above it takes an iterator of Result<ProcessLine, anyhow::Error> and converts it to a Result<Vec<ProcessLine>, anyhow::Error>.
if all the items are Ok it will go the happy path and generate the vector of ProcessLine, but if any item is Err it will stop and generate the error path with that error.
https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
also note that I can use the question mark here, because i changed the signature of the main function.

keep the for loop if it is easier to read, but look at lines and split_once.
lines is a bit clearer than split('\n'), and it will also handle some edge cases that could be useful if you want to split files into lines.
https://doc.rust-lang.org/std/primitive.str.html#method.lines
split_once will give you an option of two values or None, so in your case you could rewrite section

        let row_arr: Vec<&str> = line.trim_start().splitn(2, ' ').collect();
        if row_arr.len() < 2 {
            continue;
        }

to

        let Some((pid, name)) = line.trim_start().split_once(' ') else {
            continue;
        };

this is a bit easier to work with as you can use pid and name instead of row_arr[1], at the same time as it is faster because you dont allocate and work with a vector
https://doc.rust-lang.org/std/primitive.str.html#method.split_once

when you create menu_collection you can write

    let mut menu_collection = vec![label("Select process or hit 'q' or esc!").colorize(Color::Red), back_button("Back")];

vec![] is a macro that will create a vector and push all items you put in between []
so you dont need to push the items after.
https://doc.rust-lang.org/std/macro.vec.html
this also works without a macro, you can write

let mut menu_collection = Vec::from([label("Select process or hit 'q' or esc!").colorize(Color::Red), back_button("Back")]);

this was added to the standard library after vec![] so you can decide what you prefer.

next section is pure opinion
adding another vector to a vector can be done with extend and iterators
so you can write

menu_collection.extend(findings.iter().map(|item| button(format!("{} - {}", item.pid, shorten(&item.name, 64)))));

instead of the for loop with push
this could also be a bit more efficient, because it can resize the vector to the exact size before pushing all the items, instead of checking if the vector is big enough for every push in the for loop.
the compiler is pretty smart so I'm not sure if this is actually the case when there are so few items added.

also pure opinion but you can write the section after run like

    let menu = mut_menu(&menu);
    let index = menu.selected_item_index() - 1;

    if index == 0 || menu.canceled() {
        println!("Exited");
        return Ok(());
    }

    if let Some(item) = findings.get(index) {
        let output = run_command("kill", &["-9", &item.pid.to_string()])?;
        println!("RES: {output}");
    } else {
        println!("Could not find {}", menu.selected_item_name());
    }
    Ok(())

using if let else syntax will allow you to handle the case when the index is not found.

also note Ok(()), thats because i changed the signature of the main function to return a Result.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions