New to rust, can anyone review code?

Hello!

I am on my first day with rust, coming from things as far back as Commodore BASIC, but mostly C#.

I wrote something that I think is very handy for Windows and WSL. I wrote a blog post about it here, but the code changes often and I don't update the blog.

I could ask several specific questions, but I was hoping that someone familiar with rust could look at this from a higher level and see what patterns I'm missing, what conventions I'm not following, what I'm misunderstanding, what I'm doing wrong, and so forth, and then afterwards I could ask the specific questions.

Is this a good place for such a discussion? If anyone is interested, thanks in advance! Please be brutal; if I should not be programming in rust, then I should not be programming in rust.

use std::env;
use std::process::Command;

//TODO: command line parameters: don't show command, show but don't invoke, etc.
fn main()
{
    // command line arguments
    let args: Vec<String> = env::args().collect();

    match env::var("WSL_DISTRO_NAME") {
        Ok(_e) => {},
        Err(_e) => {
            if cfg!(target_os = "windows") {} else {
                println!("");
                println!("{0} : ", args[0]);
                println!("Define WSL_DISTRO_NAME environment variable to override.");
                println!("");
                help("Runs only under Windows and Windows Subsystem for Linux (WSL). Define WSL_DISTRO_NAME environment variable to override.", args);
                std::process::exit(0);
            }
        },
    }
    
    // first command line argument
    let mut first = String::from("");

    if args.len() > 1
    {
        first += &args[1].to_lowercase();
    }

    if first == ""
        || first == "help"
        || first == "-help" 
        || first == "-h" 
        || first == "--h"
        || first == "/h"
        || first == "-?" 
        || first == "--?"
        || first == "/?" {
            help("Help requested or no command specified", args);
            std::process::exit(0);
    }

    // WINDOWS command line to invoke - start in background
    let mut cmd = String::from("").to_owned();

    // first command line argument indicates command to invoke; copy its characters
    let compare: &str = &first[..first.len()];

    // switch on that first command line argument
    //TODO: configuration file

    match compare {
        // for testing an error condition

        "fail" => cmd += "donotwork", 
        "raw" => cmd += "start /b", 

        // common windows applications
        "exp" => cmd += "explorer.exe", // file system explorer and general launcher
        "rd" => cmd += "mstsc.exe", // remote desktop client
        "edge" => cmd += "C:/progra~2/Microsoft/Edge/Application/msedge.exe --inprivate --ash-force-desktop --disable-background-mode --disable-preconnect --new-window --dns-prefetch-disable --no-pings --process-per-tab --no-referrers --start-maximized", 
        "trackme" => cmd += "C:/progra~2/Microsoft/Edge/Application/msedge.exe --ash-force-desktop --disable-background-mode --disable-preconnect --new-window --dns-prefetch-disable --no-pings --process-per-tab --no-referrers --start-maximized", 
        "mp" => cmd += "C:/progra~2/window~4/wmplayer.exe /prefetch:1",

        //TODO: acess environment variables for paths??  
        // progra~2 is not consistent, but Program Files  (x86) causes problems due to parenthesis
        "vs" => cmd += "c:/PROGRA~2/Microsoft Visual Studio/2019/Community/Common7/IDEdevenv.exe",

        //  just type code in cmd.exe or bash.exe, not wince code.
        "code" => cmd += "C:/Users/ms/AppData/Local/Programs/Microsoft VS Code/Code.exe", 
        "rider" => cmd += "C:/Program Files/JetBrains/JetBrains Rider 2021.1.2/bin/rider64.exe",
        "flp" => cmd += "C:/Program Files/Mythicsoft/FileLocator Pro/FileLocatorPro.exe",
        "winmerge" => cmd += "C:/Progra~2/WinMerge/WinMergeU.exe",
        "tb" => cmd += "C:/Progra~2/Mozilla Thunderbird/thunderbird.exe",
        "excel" => cmd += "C:/Program Files/Microsoft Office/root/Office16/EXCEL.EXE", 
        "winword" => cmd += "C:/Program Files/Microsoft Office/root/Office16/WINWORD.EXE", 
        "outlook" => cmd += "C:/Program Files/Microsoft Office/root/Office16/OUTLOOK.EXE",
        "ppt" => cmd += "C:/Program Files/Microsoft Office/root/Office16/POWERPNT.EXE", 
        "sub" => cmd += "C:/progra~1/Sublime Text 3/sublime_text.exe",
        // https://live.sysinternals.com/ https://docs.microsoft.com/en-us/sysinternals/
        "bginfo" => cmd += "start /b \\\\live.sysinternals.com\\tools\\bginfo64.exe -accepteula", 
        "handle" => cmd += "\\\\live.sysinternals.com\\tools\\handle64.exe -accepteula", 
        "whoson" => cmd += "\\\\live.sysinternals.com\\tools\\PsLoggedon64.exe -accepteula", 
        "listdlls" => cmd += "\\\\live.sysinternals.com\\tools\\listdlls64.exe -accepteula", 
        "procexp" => cmd += "\\\\live.sysinternals.com\\tools\\procexp64.exe -accepteula", 
        "pslist" => cmd += "\\\\live.sysinternals.com\\tools\\pslist64.exe -accepteula", 
        "pskill" => cmd += "\\\\live.sysinternals.com\\tools\\pskill64.exe -accepteula", 
        "procmon" => cmd += "\\\\live.sysinternals.com\\tools\\procmon64.exe -accepteula", 
        "autoruns" => cmd += "\\\\live.sysinternals.com\\tools\\autoruns64.exe -accepteula", 
        "diskview" => cmd += "\\\\live.sysinternals.com\\tools\\diskview64.exe -accepteula", 
        "du" => cmd += "\\\\live.sysinternals.com\\tools\\du64.exe -accepteula", 

        // WSL commands
        "path" => cmd += "wslpath.exe -w", // convert WSL path to windows path

        // shutdown commands
        "hyb" => cmd += "shutdown.exe /h", // hybernate in 30 seconds unless shutdown.exe /a
        "down" => cmd += "shutdown.exe /s", // shut down in 30 seconds unless shutdown.exe /a
        "boot" => cmd += "shutdown.exe /g", //reboot in 30 seconds unless shutdown.exe /a
        "logoff" => cmd += "shutdown.exe /l", // logoff in 30 seconds unless shutdown.exe /a
        "firmware" => cmd += "shutdown.exe /r /fw", // reboot to formware in 30 seconds unless shutdown.exe /a
        "bootopt" => cmd += "shutdown.exe /r /o", // reboot to boot options in 30 seconds unless shutdown.exe /a

        // third-party commands
        "stb" => cmd += "nircmd.exe win show class Shell_TrayWnd", // show taskbar (requries nircmd)
        "htb" => cmd += "nircmd.exe win hide class Shell_TrayWnd", // hide taskbar (requries nircmd)

        // Windows control panels
        // https://wslguy.net/2021/01/21/launch-windows-features-from-wsl-shells/
        "devman" => cmd += "cmd.exe /c hdwwiz.cpl", // device manager
        "inet" => cmd += "cmd.exe /c inetcpl.cpl", 
        "joy" => cmd += "cmd.exe /c joy.cpl", // game controllers
        "sound" => cmd += "cmd.exe /c mmsys.cpl", 
        "power" => cmd += "cmd.exe /c powercfg.cpl", 
        "netcon" => cmd += "cmd.exe /c ncpa.cpl", // explorer.exe shell:::{7007ACC7-3202-11D1-AAD2-00805FC1270E}
        "sysprop" => cmd += "cmd.exe /c sysdm.cpl", 
        "loc" => cmd += "cmd.exe /c telephon.cpl", 
        "dt" => cmd += "cmd.exe /c timedate.cpl", 
        "secman" => cmd += "cmd.exe /c wscui.cpl", // security and maintenance
        "programs" => cmd += "cmd.exe /c appwiz.cpl", //"explorer.exe shell:::{7b81be6a-ce2b-4676-a29e-eb907a5126c5}", // change/remove programs
        "firewall" => cmd += "cmd.exe /c firewall.cpl", //"explorer.exe shell:::{4026492F-2F69-46B8-B9BF-5654FC07E423}", 
        "mouse" => cmd += "cmd.exe /c main.cpl",

        // Windows features known by name
        // https://www.linkedin.com/pulse/wsl-script-access-windows-functions-code-john-west/
        "volume" => cmd += "start /b ms-settings:apps-volume",  // volume
        "display" => cmd += "start /b ms-settings:display", // desk.cpl
        "mouseset" => cmd += "start /b ms-settings:mousetouchpad",
        "personal" => cmd += "start /b ms-settings:personalization", //TODO: same as person?
        "background" => cmd += "start /b ms-settings:personalization-background",
        "colors" => cmd += "start /b ms-settings:personalization-colors",
        "lock" => cmd += "start /b ms-settings:lockscreen",
        "start" => cmd += "start /b ms-settings:personalization-start", // start menu settings
        "startfol" => cmd += "start /b ms-settings:personalization-start-places", // folders on start menu
        "taskbar" => cmd += "start /b ms-settings:taskbar",
        "allstart" => cmd += "start /b shell:common startup", // all users startup folder
        "startup" => cmd += "start /b shell:startup", // current user startup folder
        "datetime" => cmd += "start /b ms-settings:dateandtime",
        "region" => cmd += "start /b ms-settings:regionformatting", // intl.cpl
        "gamebar" => cmd += "start /b ms-settings:gaming-gamebaar",
        "pointer" => cmd += "start /b ms-settings:easeofaccess-MousePointer",
        "update" => cmd += "start /b ms-settings:windowsupdate-action",
        "dev" => cmd += "start /b ms-settings:developers",
        "privacy" => cmd += "start /b ms-settings:privacy",
        "settings" => cmd += "start /b ms-settings:",
        "desktop" => cmd += "start /b shell:Desktop", // shell:::{00021400-0000-0000-C000-000000000046}", // error? 
        "apps" => cmd += "start /b shell:AppsFolder", // {4234d49b-0245-4df3-b780-3893943456e1}", // all applications
        "recyc" => cmd += "start /b shell:RecycleBinFolder",

        // Windows features known only by GUID
        "admin" => cmd += "start /b shell:::{D20EA4E1-3957-11d2-A40B-0C5020524153}",
        "control" => cmd += "start /b shell:::{21EC2020-3AEA-1069-A2DD-08002B30309D}", // control panel small icons
        "tasks" => cmd += "start /b shell:::{5ED4F38C-D3FF-4D61-B506-6820320AEBFE}",
        "cpcats" => cmd += "start /b shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}", // control panel categories
        "defaults" => cmd += "start /b shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}", // default programs
        "printers" => cmd += "start /b shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}",
        "disp" => cmd += "start /b shell:::{C555438B-3C23-4769-A71F-B6D3D9B6053A}", // error
        "email" => cmd += "start /b shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}",
        "fileopt" => cmd += "start /b shell:::{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}", // file system explorer options
        "onedrive" => cmd += "start /b shell:::{018D5C66-4533-4307-9B53-224DE2ED1FE6}",
        "person" => cmd += "start /b shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}", // personalization
        "remove" => cmd += "start /b shell:::{a6482830-08eb-41e2-84c1-73920c2badb9}", // removable storage devices
        "system" => cmd += "start /b shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}", // system info
        "pc" => cmd += "start /b shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", // file explorer exe "This Computer"
        "trouble" => cmd += "start /b shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}", // Windows troubleshooting Windows? hahahahaha...
        "users" => cmd += "start /b shell:::{60632754-c523-4b62-b45c-4172da012619}", // users
        "user" => cmd += "start /b shell:::{60632754-c523-4b62-b45c-4172da012619}", // current user
        "features" => cmd += "start /b shell:::{67718415-c450-4f3c-bf8a-b487642dc39b}", // Windows features
        "mobility" => cmd += "start /b shell:::{5ea4f148-308c-46d7-98a9-49041b1dd468}",

        // no match
        _ => { 
            help(&format!("invalid command: {0}", compare), args); 
            std::process::exit(0);
        },
    }

    if args.len() > 1 {
        // append a space before subsequent parameters
        for n in 2..args.len() {
            cmd += " \"";
            cmd += &args[n].to_string();
            cmd += "\"";
        }
    }

    run(&cmd);
    std::process::exit(0);
}

fn run(command: &str) {
    let line = command;//str::replace(command, "'", "\"");
    println!("{}", line);
    let _output = Command::new("cmd.exe")
        .args(&["/c", &line]).spawn();
}

fn help(msg: &str, args: Vec<String>)
{
    let text: &'static str = 







    
"fail        Test Error                 devman      Device Manager          
exp         Windows Explorer           inet        Internet Options        
rd          Remote Desktop             joy         Game Controllers        
vs          Visual Studio              sound       Sound Options           
code        Visual Studio Code         power       Power Options           
edge        Edge Browser (InPrivate)   netcon      Network Connections     
trackme     Edge Browser               privacy     Privacy Settings
mp          Media Player               sysprop     System Properties       
bginfo      Background Info            loc         Localization Options    
handle      File Handles               dt          Date & Time Options     
whoson      Who's Logged On            secman      Security & Maintenance  
listdlls    List Assemblies            programs    Change/Remove Programs  
procexp     Process Explorer           firewall    Windows Firewall        
pslist      Process List               mouse       Mouse Options           
pskill      Kill Process               volume      Volume                  
procmon     Monitor Processes          display     Display                 
autoruns    Startup Processes          mousesset   Mouse Settings          
diskview    Disk Space Viewer          personal    Personalization         
du          Disk Usage                 background  Desktop Backgrond       
path        wslpath -w                 colors      Windows Colors          
hyb         Hybernate                  lock        Lock Screen Settings    
down        Shutdown                   start       Start Menu Settings     
boot        Reboot                     startfol    Start Folders           
logoff      Log Off                    taskbar     Taskbar Settings        
firmware    Reboot to Firmware         allstart    Startup Folder (All)    
bootopt     Reboot to Options          startup     Startup Folder (User)   
stb         Show taskbar               start       Start Menu Settings
htb         Hide taskbar               datetime    Date/Time
region      Region                     features    Add/Remove Windows Features
gamebar     Game Bar                   printers    Printers
pointer     Mouse Pointer              disp        Display
update      Windows Update             email       Email Accounts
dev         Developer Settings         fileopt     File Explorer Options
apps        All Applications           onedrive    OneDrive
admin       Administrative Tools       system      System
settings    Settings                   pc          This Computer
control     Control Panel              trouble     Troubleshoot Windows
cpcats      (Categories)               user        User Settings
defaults    Default Programs           users       Windows Users
mobility    Windows Mobility Center    desktop     Desktop Folder
recyc       Recycle Bin Folder         rider       JetBrains Rider
flp         File Locator Pro           winmerge    WinMerge
tb          Thunderbird                excel       Microsoft Excel
winword     Microsoft Word             ppt         Microsoft PowerPoint
outlook     Microsoft Outlook          sub         Sublime Text Editor
tasks";
    println!("");
    println!("{0} : access Windows features : {1}", args[0], msg);
    println!("");
    println!("{0} : usage :", args[0]);
    println!("");
    println!("{0} help", args[0]);
    println!("{0} <code>", args[0]);
    println!("{0} raw <command>", args[0]);
    println!("");
    println!("{0}", text);
    println!("");
}

Here are the things I notice:


let mut cmd = String::from("").to_owned();

Here you are converting into a String twice. Just do it once.


let compare: &str = &first[..first.len()];

If you want to convert from String to &str, just use the .as_str() method. You can also write &first[..] without any start or end index.


If you are able to, I would not use cmd.exe /c [cmd] to execute your command. Instead, I would define a vector of arguments and spawn the command directly without going through cmd.exe.


Instead of using std::process::exit(0), just return from the main function with a return.


Using { on its own line is unidiomatic Rust.

2 Likes

When you want an empty String, you can just use

let mut cmd = String::new();

A line continuation escape in a str will eat the newline and all following whitespace, so you can make text blobs a bit more ergonomic like so (note the \n\):

    let text: &'static str = "\
        fail        Test Error                 devman      Device Manager\n\
        exp         Windows Explorer           inet        Internet Options\n\
        rd          Remote Desktop             joy         Game Controllers\n\
        [...]"

I'd rewrite your appending loop like so:

    for arg in args.iter().skip(2) {
        cmd += " \"";
        cmd += arg;
        cmd += "\"";
    }

But even if you're not yet comfortable with iterators, you don't need to clone the string, and can use:

        cmd += &args[n];
2 Likes

I'll add that cargo fmt is a great way to get idiomatic formatting.

In general it’s really useful to use clippy.

warning: using `println!("")`
  --> src/main.rs:14:17
   |
14 |                 println!("");
   |                 ^^^^^^^^^^^^ help: replace it with: `println!()`
   |
   = note: `#[warn(clippy::println_empty_string)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: using `println!("")`
  --> src/main.rs:17:17
   |
17 |                 println!("");
   |                 ^^^^^^^^^^^^ help: replace it with: `println!()`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: using `println!("")`
  --> src/main.rs:40:13
   |
40 |             println!("");
   |             ^^^^^^^^^^^^ help: replace it with: `println!()`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: using `println!("")`
  --> src/main.rs:42:13
   |
42 |             println!("");
   |             ^^^^^^^^^^^^ help: replace it with: `println!()`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: using `println!("")`
   --> src/main.rs:177:13
    |
177 |             println!("");
    |             ^^^^^^^^^^^^ help: replace it with: `println!()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: using `println!("")`
   --> src/main.rs:179:13
    |
179 |             println!("");
    |             ^^^^^^^^^^^^ help: replace it with: `println!()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string

warning: redundant clone
  --> src/main.rs:47:35
   |
47 |     let mut cmd = String::from("").to_owned();
   |                                   ^^^^^^^^^^^ help: remove this
   |
   = note: `#[warn(clippy::redundant_clone)]` on by default
note: this value is dropped without further use
  --> src/main.rs:47:19
   |
47 |     let mut cmd = String::from("").to_owned();
   |                   ^^^^^^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone

warning: comparison to empty slice
  --> src/main.rs:31:8
   |
31 |     if first == ""
   |        ^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `first.is_empty()`
   |
   = note: `#[warn(clippy::comparison_to_empty)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty

warning: the loop variable `n` is only used to index `args`
   --> src/main.rs:188:18
    |
188 |         for n in 2..args.len() {
    |                  ^^^^^^^^^^^^^
    |
    = note: `#[warn(clippy::needless_range_loop)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
help: consider using an iterator
    |
188 |         for <item> in args.iter().skip(2) {
    |             ^^^^^^    ^^^^^^^^^^^^^^^^^^^

warning: 9 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
4 Likes
if first == ""
        || first == "help"
        || first == "-help" 
        || first == "-h" 
        || first == "--h"
        || first == "/h"
        || first == "-?" 
        || first == "--?"
        || first == "/?"

can be shortened using a slice and .contains or the matches! macro:

    if ["", "help", "-help", "-h", "--h", "/h", "-?", "--?", "/?"].contains(&first.as_str()) {
        println!();
        println!("{0} : Help requested or no command specified.", args[0]);
        println!();
        help();
    }

or

if matches!(first.as_str(), ""| "help"| "-help"| "-h"| "--h"| "/h"| "-?"| "--?"| "/?") {
        println!();
        println!("{0} : Help requested or no command specified.", args[0]);
        println!();
        help();
    }

    let mut first = String::from("");

    if args.len() > 1 {
        first += &args[1].to_lowercase();
    }

    // first never mutated again

you only mutate first for defining it, using e.g. match on args.get(1) seems a lot cleaner:

    let first = match args.get(1) {
        Some(arg) => arg.to_lowercase(),
        None => String::new(),
    };

You do some

    // WINDOWS command line to invoke - start in background
    let mut cmd = String::from("").to_owned();

    // first command line argument indicates command to invoke; copy its characters
    let compare: &str = &first[..first.len()];

    // switch on that first command line argument
    //TODO: configuration file

    match compare {
        // for testing an error condition

        "fail" => cmd += "donotwork", 
        "raw" => cmd += "start /b", 
        // etc…

        // no match
        _ => { 
            help(&format!("invalid command: {0}", compare), args); 
            std::process::exit(0);
        },
    }

but you could save typing a lot of cmd +=

    // WINDOWS command line to invoke - start in background
    let mut cmd = match first.as_str() {
        // for testing an error condition

        "fail" => "donotwork", 
        "raw" => "start /b", 
        // etc…

        // no match
        _ => { 
            help(&format!("invalid command: {0}", first.as_str()), args); 
            return;
        },
    }.to_owned();
3 Likes

I would like to individually thank everyone that responded and apologize that I do not have time to understand/remember/apply all of it or explain all of this. As I don't seem to go a few seconds without processing a string and I still don't really understand string processing in rust, I almost gave up, but somehow stumbled this much further. Thanks again for all of the help!

/************************************************************************************************
wince: windows command invoker : Invoke Windows commands from shells including WSL
rustc wince.rs ;  wince help
//TODO: program could handle "show" command to output command to invoke without invoking, etc.
//TODO: untested: cmd.exe and explorer.exe
************************************************************************************************/

use std::env;
use std::process::Command;

// configuration for a command to invoke
struct Invoker {
    command: String,        // [command]
    use_cmd_exe: bool,      // cmd.exe [command]
    use_start: bool,        // cmd.exe /start [command]
    background: bool,       // cmd.exe /start /b [command]
    use_explorer: bool,     // [cmd.exe] explorer.exe [/start | start /b]] [command]
    arguments: Vec<String>, // [explorer.exe | cmd.exe [/start | start /b] [command]] [arguments]
}

// TODO: eliminate redundancies
impl Invoker {
    // default - invoke binary
    pub fn new<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        let mut inv = Invoker {
            use_cmd_exe: false,
            background: false,
            use_start: false,
            use_explorer: false,
            command: cmd.into(),
            arguments: vec![],
        };

        for arg in args.iter() {
            inv.arguments.push(arg.to_string());
        }

        //clippy reports unneeded `return` statement, but if removed, then
        // error[E0308]: mismatched types pub fn new<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        //   |            ---                                                ^^^^^^^ expected struct `Invoker`, found `()`
        return inv;
    }

    // run in the background with cmd.exe start /b
    pub fn bkg<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        let mut inv = Invoker {
            use_cmd_exe: true,
            background: true,
            use_start: true,
            use_explorer: false,
            command: cmd.into(),
            arguments: vec![],
        };

        for arg in args.iter() {
            inv.arguments.push(arg.to_string());
        }

        return inv;
    }

    // use explorer.exe
    pub fn exp<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        let mut inv = Invoker {
            use_cmd_exe: false,
            background: false,
            use_start: false,
            use_explorer: true,
            command: cmd.into(),
            arguments: vec![],
        };

        for arg in args.iter() {
            inv.arguments.push(arg.to_string());
        }

        return inv;
    }

    pub fn cmdexe<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        let mut inv = Invoker {
            use_cmd_exe: true,
            background: false,
            use_start: false,
            use_explorer: false,
            command: cmd.into(),
            arguments: vec![],
        };

        for arg in args.iter() {
            inv.arguments.push(arg.to_string());
        }

        return inv;
    }

    /*unused
    pub fn start<Cmd: Into<String>>(cmd: Cmd, args: &[&str]) -> Invoker {
        let mut inv = Invoker {
            use_cmd_exe: true,
            background: false,
            use_start: true,
            use_explorer: false,
            command: cmd.into(),
            arguments: vec![],
        };

        for arg in args.iter() {
            inv.arguments.push(arg.to_string());
        }

        inv;
    }*/
}

fn main() {
    // command line arguments
    let args: Vec<String> = env::args().collect();

    // only runs under WSL or Windows
    match env::var("WSL_DISTRO_NAME") {
        Ok(_e) => {}
        Err(_e) => {
            if !cfg!(target_os = "windows") {
                help("Runs only under Windows and Windows Subsystem for Linux (WSL). Define WSL_DISTRO_NAME environment variable to override.", args);
                return;
            }
        }
    }

    let first_arg = match args.get(1) {
        Some(arg) => arg.to_lowercase(),
        None => String::new(),
    };

    if matches!(
        first_arg.as_str(),
        "" | "help" | "-help" | "-h" | "--h" | "/h" | "-?" | "--?" | "/?"
    ) {
        help("Help requested or no command specified", args);
        return;
    }

    //TODO: how to add signatures to avoid the empty string and argument passes?
    let invoker = match first_arg.as_str() {
        "raw" => Invoker::cmdexe("", &[]), // cmd.exe start [command line]
        "exp" => Invoker::exp("", &[]),    // Windows File System explorer

        "rd" => Invoker::bkg("mstsc.exe", &[]), // remote desktop client
        "vs" => Invoker::new(
            "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/IDE/devenv.exe",
            &[],
        ), //TODO: bkg()
        "code" => Invoker::new(
            "C:/Users/ms/AppData/Local/Programs/Microsoft VS Code/Code.exe",
            &[],
        ),
        "rider" => Invoker::new(
            "C:/Program Files/JetBrains/JetBrains Rider 2021.1.2/bin/rider64.exe",
            &[],
        ),
        "flp" => Invoker::new(
            "C:/Program Files/Mythicsoft/FileLocator Pro/FileLocatorPro.exe",
            &[],
        ),
        "winmerge" => Invoker::new("C:/Program Files (x86)/WinMerge/WinMergeU.exe", &[]),
        "tb" => Invoker::new(
            "C:/Program Files (x86)/Mozilla Thunderbird/thunderbird.exe",
            &[],
        ),
        "excel" => Invoker::new(
            "C:/Program Files/Microsoft Office/root/Office16/EXCEL.EXE",
            &[],
        ),
        "winword" => Invoker::new(
            "C:/Program Files/Microsoft Office/root/Office16/WINWORD.EXE",
            &[],
        ),
        "outlook" => Invoker::new(
            "C:/Program Files/Microsoft Office/root/Office16/OUTLOOK.EXE",
            &[],
        ),
        "ppt" => Invoker::new(
            "C:/Program Files/Microsoft Office/root/Office16/POWERPNT.EXE",
            &[],
        ),
        "sub" => Invoker::new("C:/Program Files/Sublime Text 3/sublime_text.exe", &[]),
        "stb" => Invoker::new("nircmd.exe", &["win", "show", "class", "Shell_TrayWnd"]), // show taskbar (requries nircmd)
        "htb" => Invoker::new("nircmd.exe", &["win", "hide", "class", "Shell_TrayWnd"]), // hide taskbar (requries nircmd)

        //        "mp" => Invoker::bkg("C:/program files/windows media player/wmplayer.exe", &[]), //TODO: background fails; unknown cause
        "mp" => Invoker::new("C:/program files/windows media player/wmplayer.exe", &[]), // should be /start /b, but seems to ignore anyway

        //TODO: avoid redundancy
        "edge" => Invoker::new(
            "C:/program files (x86)/Microsoft/Edge/Application/msedge.exe",
            &[
                "--inprivate",
                "--ash-force-desktop",
                "--disable-background-mode",
                "--disable-preconnect",
                "--new-window",
                "--dns-prefetch-disable",
                "--no-pings",
                "--process-per-tab",
                "--no-referrers",
                "--start-maximized",
            ],
        ),

        "trackme" => Invoker::new(
            "C:/program files (x86)/Microsoft/Edge/Application/msedge.exe",
            &[
                //                "--inprivate",
                "--ash-force-desktop",
                "--disable-background-mode",
                "--disable-preconnect",
                "--new-window",
                "--dns-prefetch-disable",
                "--no-pings",
                "--process-per-tab",
                "--no-referrers",
                "--start-maximized",
            ],
        ),

        // https://live.sysinternals.com/ https://docs.microsoft.com/en-us/sysinternals/
        "bginfo" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\bginfo64.exe",
            &["-accepteula"],
        ),
        "handle" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\handle64.exe",
            &["-accepteula"],
        ),
        "whoson" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\PsLoggedon64.exe",
            &["-accepteula"],
        ),
        "listdlls" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\listdlls64.exe",
            &["-accepteula"],
        ),
        "procexp" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\procexp64.exe",
            &["-accepteula"],
        ),
        "pslist" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\pslist64.exe",
            &["-accepteula"],
        ),
        "pskill" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\pskill64.exe",
            &["-accepteula"],
        ),
        "procmon" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\procmon64.exe",
            &["-accepteula"],
        ),
        "autoruns" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\autoruns64.exe",
            &["-accepteula"],
        ),
        "diskview" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\diskview64.exe",
            &["-accepteula"],
        ),
        "du" => Invoker::cmdexe(
            "\\\\live.sysinternals.com\\tools\\du64.exe",
            &["-accepteula"],
        ),

        // TODO: WSL commands
        //        "path" => Invoker::new("wslpath", &["-w"]), // convert WSL path to windows path //TODO: use wsl_path_or_self

        // shutdown commands
        "hyb" => Invoker::new("shutdown.exe", &["/h"]), // hybernate in 30 seconds unless shutdown.exe /a
        "down" => Invoker::new("shutdown.exe", &["/s"]), // shut down in 30 seconds unless shutdown.exe /a
        "boot" => Invoker::new("shutdown.exe", &["/g"]), //reboot in 30 seconds unless shutdown.exe /a
        "logoff" => Invoker::new("shutdown.exe", &["/l"]), // logoff in 30 seconds unless shutdown.exe /a
        "firmware" => Invoker::new("shutdown.exe", &["/r", "/fw"]), // reboot to formware in 30 seconds unless shutdown.exe /a
        "bootopt" => Invoker::new("shutdown.exe", &["/r", "/o"]), // reboot to boot options in 30 seconds unless shutdown.exe /a

        // Windows control panels
        // https://wslguy.net/2021/01/21/launch-windows-features-from-wsl-shells/
        "devman" => Invoker::cmdexe("hdwwiz.cpl", &[]), // device manager
        "inet" => Invoker::cmdexe("inetcpl.cpl", &[]),
        "joy" => Invoker::cmdexe("joy.cpl", &[]), // game controllers
        "sound" => Invoker::cmdexe("mmsys.cpl", &[]),
        "power" => Invoker::cmdexe("powercfg.cpl", &[]),
        "netcon" => Invoker::cmdexe("ncpa.cpl", &[]), // explorer.exe shell:::{7007ACC7-3202-11D1-AAD2-00805FC1270E}
        "sysprop" => Invoker::cmdexe("sysdm.cpl", &[]),
        "loc" => Invoker::cmdexe("telephon.cpl", &[]),
        "dt" => Invoker::cmdexe("timedate.cpl", &[]),
        "secman" => Invoker::cmdexe("wscui.cpl", &[]), // security and maintenance
        "programs" => Invoker::cmdexe("appwiz.cpl", &[]), //"explorer.exe shell:::{7b81be6a-ce2b-4676-a29e-eb907a5126c5}", // change/remove programs
        "firewall" => Invoker::cmdexe("firewall.cpl", &[]), //"explorer.exe shell:::{4026492F-2F69-46B8-B9BF-5654FC07E423}",
        "mouse" => Invoker::cmdexe("main.cpl", &[]),

        // Windows features known by name
        // https://www.linkedin.com/pulse/wsl-script-access-windows-functions-code-john-west/
        "vol" => Invoker::exp("ms-settings:apps-volume", &[]), // volume
        "display" => Invoker::exp("ms-settings:display", &[]), // desk.cpl
        "mouseset" => Invoker::exp("ms-settings:mousetouchpad", &[]),
        "personal" => Invoker::exp("ms-settings:personalization", &[]), //TODO: same as person?
        "background" => Invoker::exp("ms-settings:personalization-background", &[]),
        "colors" => Invoker::exp("ms-settings:personalization-colors", &[]),
        "lock" => Invoker::exp("ms-settings:lockscreen", &[]),
        "start" => Invoker::exp("ms-settings:personalization-start", &[]), // start menu settings
        "startfol" => Invoker::exp("ms-settings:personalization-start-places", &[]), // folders on start menu
        "taskbar" => Invoker::exp("ms-settings:taskbar", &[]),
        "allstart" => Invoker::exp("shell:common startup", &[]), // all users startup folder
        "startup" => Invoker::exp("shell:startup", &[]),         // current user startup folder
        "datetime" => Invoker::exp("ms-settings:dateandtime", &[]),
        "region" => Invoker::exp("ms-settings:regionformatting", &[]), // intl.cpl
        "gamebar" => Invoker::exp("ms-settings:gaming-gamebaar", &[]),
        "pointer" => Invoker::exp("ms-settings:easeofaccess-MousePointer", &[]),
        "update" => Invoker::exp("ms-settings:windowsupdate-action", &[]),
        "dev" => Invoker::exp("ms-settings:developers", &[]),
        "privacy" => Invoker::exp("ms-settings:privacy", &[]),
        "settings" => Invoker::exp("ms-settings:", &[]),
        "desktop" => Invoker::exp("shell:Desktop", &[]), // shell:::{00021400-0000-0000-C000-000000000046}", // error?
        "apps" => Invoker::exp("shell:AppsFolder", &[]), // {4234d49b-0245-4df3-b780-3893943456e1}", // all applications
        "recyc" => Invoker::exp("shell:RecycleBinFolder", &[]),

        // Windows features known only by GUID
        "admin" => Invoker::exp(
            "start /b shell:::{D20EA4E1-3957-11d2-A40B-0C5020524153}",
            &[],
        ),
        "control" => Invoker::exp("shell:::{21EC2020-3AEA-1069-A2DD-08002B30309D}", &[]), // control panel small icons
        "tasks" => Invoker::exp("shell:::{5ED4F38C-D3FF-4D61-B506-6820320AEBFE}", &[]),
        "cpcats" => Invoker::exp("shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}", &[]), // control panel categories
        "defaults" => Invoker::exp("shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}", &[]), // default programs
        "printers" => Invoker::exp("shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}", &[]),
        "disp" => Invoker::exp("shell:::{C555438B-3C23-4769-A71F-B6D3D9B6053A}", &[]), // error
        "email" => Invoker::exp("shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}", &[]),
        "fileopt" => Invoker::exp("shell:::{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}", &[]), // file system explorer options
        "onedrive" => Invoker::exp("shell:::{018D5C66-4533-4307-9B53-224DE2ED1FE6}", &[]),
        "person" => Invoker::exp("shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}", &[]), // personalization
        "remove" => Invoker::exp("shell:::{a6482830-08eb-41e2-84c1-73920c2badb9}", &[]), // removable storage devices
        "system" => Invoker::exp("shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}", &[]), // system info
        "pc" => Invoker::exp("shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", &[]), // file explorer exe "This Computer"
        "trouble" => Invoker::exp("shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}", &[]), // Windows troubleshooting Windows? hahahahaha...
        "users" => Invoker::exp("shell:::{60632754-c523-4b62-b45c-4172da012619}", &[]),   // users
        "user" => Invoker::exp("shell:::{60632754-c523-4b62-b45c-4172da012619}", &[]), // current user
        "features" => Invoker::exp("shell:::{67718415-c450-4f3c-bf8a-b487642dc39b}", &[]), // Windows features
        "mobility" => Invoker::exp("shell:::{5ea4f148-308c-46d7-98a9-49041b1dd468}", &[]),

        // no match
        _ => {
            help(&format!("invalid command: {0}", first_arg.as_str()), args);
            return;
        }
    };

    // the executable to invoke. Assumes only Windows paths passed from cmd.exe/powershell.
    let maybe_executable = wsl_path_or_self(invoker.command.as_str(), !cfg!(target_os = "windows"));
    let cmd: &str;

    // if directed to use cmd.exe or start or start /b, then use cmd.exe /c
    // else if directed to use explorer, then use explorer.exe
    // otherwise invoke the command directly
    if invoker.use_cmd_exe || invoker.use_start || invoker.background {
        cmd = "cmd.exe";
    } else if invoker.use_explorer {
        cmd = "explorer.exe";
    } else {
        cmd = maybe_executable.as_str();
    }

    // the command to execute
    let mut torun = Command::new(String::from(cmd));

    if invoker.use_cmd_exe {
        torun.arg("/c");
    }

    // start and start /b require start
    if invoker.use_start || invoker.background {
        torun.arg("start");
    }

    // start /b
    if invoker.background {
        torun.arg("/b");
    };

    // if executable specified with cmd.exe then add windows path to executable to command line
    if invoker.command.is_empty()// != ""
        && (invoker.use_cmd_exe || invoker.use_start || invoker.background || invoker.use_explorer)
    {
        torun.arg(wsl_path_or_self(&invoker.command, false));
    }

    // add arguments from command configuration
    for arg in invoker.arguments.iter() {
        torun.arg(wsl_path_or_self(arg, false).to_owned().replace("\\", "/"));
    }

    // add any unprocessed arguments from command line to the list of arguments
    for arg in args.iter().skip(2) {
        torun.arg(wsl_path_or_self(arg, false).replace("\\", "/"));
    }

    if invoker.background {
        let _discard = torun.status();
    } else {
        let results = torun.output().expect("failed to execute process");
        //TODO: show command line

        let err = String::from_utf8_lossy(&results.stderr);

        if err != "" {
            println!("{}", err.replace('n', ""));
        }

        let out = String::from_utf8_lossy(&results.stdout);

        if out != "" {
            println!("{}", out.replace('\n', ""));
        }
        //        println!("{}", results.status.code().to_string());
    }
}

// convert between Unix and Windows file paths
// when running in Windows, return arg.
// when running under Bash, invoke wslpath.
// unix true will pass -u (convert Windows path to Unix)
// unix false will pass -w (convert Unix path to Windows)
fn wsl_path_or_self(arg: &str, unix: bool) -> String {
    if cfg!(target_os = "windows") {
        // clippy issue similar to return inv above
        return arg.to_string();
    } else {
        let mut to_run = Command::new("wslpath");
        if unix {
            to_run.arg("-u");
        } else {
            to_run.arg("-w");
        }
        to_run.arg(arg);
        let results = to_run.output().expect("failed to execute process");
        match results.status.code() {
            Some(0) => {
                return String::from(
                    &String::from_utf8_lossy(&results.stdout)
                        .to_owned()
                        .to_string()
                        .replace('\n', ""),
                );
            }
            _ => {
                return arg.to_string();
            }
        }
    }
}

// show help information for command.
// accepts an error/help message and command line arguments.
fn help(msg: &str, args: Vec<String>) {
    println!();
    println!("{0} : access Windows features : {1}", args[0], msg);
    println!();
    println!("{0} : usage :", args[0]);
    println!();
    println!("{0} help", args[0]);
    println!("{0} <code>", args[0]);
    println!("{0} raw <command>", args[0]);
    println!();
    println!(
        "\
                                           devman      Device Manager\n\
    exp         Windows Explorer           inet        Internet Options\n\
    rd          Remote Desktop             joy         Game Controllers\n\
    vs          Visual Studio              sound       Sound Options\n\
    code        Visual Studio Code         power       Power Options\n\
    edge        Edge Browser (InPrivate)   netcon      Network Connections\n\
    trackme     Edge Browser               privacy     Privacy Settings\n\
    mp          Media Player               sysprop     System Properties\n\
    bginfo      Background Info            loc         Localization Options\n\
    handle      File Handles               dt          Date & Time Options\n\
    whoson      Who's Logged On            secman      Security & Maintenance\n\
    listdlls    List Assemblies            programs    Change/Remove Programs\n\
    procexp     Process Explorer           firewall    Windows Firewall\n\
    pslist      Process List               mouse       Mouse Options\n\
    pskill      Kill Process               vol         Volume\n\
    procmon     Monitor Processes          display     Display\n\
    autoruns    Startup Processes          mousesset   Mouse Settings\n\
    diskview    Disk Space Viewer          personal    Personalization\n\
    du          Disk Usage                 background  Desktop Backgrond\n\
    path        wslpath -w                 colors      Windows Colors\n\
    hyb         Hybernate                  lock        Lock Screen Settings\n\
    down        Shutdown                   start       Start Menu Settings\n\
    boot        Reboot                     startfol    Start Folders\n\
    logoff      Log Off                    taskbar     Taskbar Settings\n\
    firmware    Reboot to Firmware         allstart    Startup Folder (All)\n\
    bootopt     Reboot to Options          startup     Startup Folder (User)\n\
    stb         Show taskbar               start       Start Menu Settings\n\
    htb         Hide taskbar               datetime    Date/Time\n\
    region      Region                     features    Add/Remove Windows Features\n\
    gamebar     Game Bar                   printers    Printers\n\
    pointer     Mouse Pointer              disp        Display\n\
    update      Windows Update             email       Email Accounts\n\
    dev         Developer Settings         fileopt     File Explorer Options\n\
    apps        All Applications           onedrive    OneDrive\n\
    admin       Administrative Tools       system      System\n\
    settings    Settings                   pc          This Computer\n\
    control     Control Panel              trouble     Troubleshoot Windows\n\
    cpcats      (Categories)               user        User Settings\n\
    defaults    Default Programs           users       Windows Users\n\
    mobility    Windows Mobility Center    desktop     Desktop Folder\n\
    recyc       Recycle Bin Folder         rider       JetBrains Rider\n\
    flp         File Locator Pro           winmerge    WinMerge\n\
    tb          Thunderbird                excel       Microsoft Excel\n\
    winword     Microsoft Word             ppt         Microsoft PowerPoint\n\
    outlook     Microsoft Outlook          sub         Sublime Text Editor\n\
    tasks"
    );
    println!();
}

If you remove both the return and the semicolon after inv, the function will return that. Using the (kind of) implicit returns like that is idiomatic Rust.

1 Like

@commodore73 On that note, clippy will have also given you an explanation link

https://rust-lang.github.io/rust-clippy/master/index.html#needless_return

needless_return


What it does

Checks for return statements at the end of a block.


Why is this bad

Removing the return and semicolon will make the code more rusty.


Known problems

None.

Search on GitHub


Example

fn foo(x: usize) -> usize {
    return x;
}

simplify to

fn foo(x: usize) -> usize {
    x
}

Thanks again! I will update the code. I do read the notes from clippy, but somehow my experience with C# (where the removal of the semicolon would cause a syntax error) override my ability to understand the note.

Specifically, where I explicitly returned:

return inv;

I could just use the following, which (without the return statement or the statement terminator) implicitly returns the specified value and confirms the return type of the function:

inv

I am still not sure that I have applied everything, but I applied what I could remember, added a few features, refactored a lot, and learned a great deal as part of the process. The code is rustier but I feel like I'm still not applying patterns very idiomatically. If anyone cares to further review any code, or the project/module structure, or the approach to docs, or anything else, I would be glad to see any feedback. There are many TODOs but I think I can address those on my own (it's a lot of code but it's a very simple program). Also, I'm not very good at reading books online, so I ordered a couple of books.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.