Quick Notes on Mutable and Immutable Variables in Rust

This blog post contains some notes about mutable and immutable variables in the rust programming language.

Rust supports both mutable and immutable variables. Mutable variables are typical of all programming languages: they hold values that can change. Variables are immutable by default. Immutable variables can hold values that cannot change, but they are not the same as constants. Constants always have a single, hard-coded value. Immutable variables accept dynamic assignment, but only once, after which their values cannot change.

The following block of code initializes a mutable integer named result and then updates its value.

pub mod wsl;
use crate::wsl::inv::invocablecategory::InvocableCategory;
use crate::wsl::inv::invocablecategorylist::InvocableCategoryList;
use crate::wsl::inv::invoker::Invoker;

pub fn run(config: WinkConfig) -> Result<u8, Box<dyn std::error::Error>> {
    // categories contain lists of invocables that map command codes to commands
    let category_list = InvocableCategoryList::get();
    let mut result: u8 = 0;

    //TODO: convert to Err()
    if !config.help_msg.is_empty() {
        help(
            &config.help_msg.to_string(),
            config,
            category_list.categories,
        );

        result = 1;
    } else if !wsl::is_windows_or_wsl() {
        help("Runs only under Windows and Windows Subsystem for Linux (WSL). Define WSL_DISTRO_NAME environment variable to override.", config, category_list.categories);
        result = 2;
    } else if config.pretty_print && !config.export {
        help("-p invalid without -e", config, category_list.categories);
        result = 3;
    } else if config.command_code.is_empty() && !(config.export || config.dry_run) {
        help("No command specified", config, category_list.categories);
        result = 4;
    } else if let Some(invocable) = category_list.get_invocable(&config.command_code) {
        if config.export {
            if config.pretty_print {
                println!("{}", serde_json::to_string_pretty(&invocable).unwrap());
            } else {
                println!("{}", serde_json::to_string(&invocable).unwrap());
            }
        }

        Invoker {}.invoke(invocable, config.dry_run, config.verbose, config.cmd_args);
        result = 0;
    } else if config.export && config.command_code.is_empty() {
        if config.pretty_print {
            println!("{}", serde_json::to_string_pretty(&category_list).unwrap());
        } else {
            println!("{}", serde_json::to_string(&category_list).unwrap());
        }

        result = 0;
    } else if (config.command_code.is_empty() || !config.export) && config.dry_run {
        result = 0;
    } else {
        help(
            &format!("Command not recognized: {0}", config.command_code),
            config,
            category_list.categories,
        );

        result = 5;
    }

    Ok(result)
}

Note that the integer values assigned to result are actually constants; they just do not have names. These are the return codes from the program that are not used, meaningful, or important enough to have names assigned to them.

One note about integers in rust (though I may have things wrong): I think that I read somewhere that overflow (not sure about underflow) panics test builds but just overflows in release builds, which might be appropriate in some cases to prevent panic/crashes in production, but differs from other languages, could lead to defects, and will not get caught without comprehensive test coverage of relatively unexpected conditions or even cases that are logically impossible in production. This seems like a strange feature for a language that is otherwise relatively strict. The return value in this code does not change, so it cannot overflow.

The code above gives result a default value, which may be considered safe in some cases, but for certainty and code clarity, each of the conditions afterwards overrides that value, even with that default value. This might function properly, but generates a warning, because why assign the zero and never use it? Is the developer aware of what is happening here? Does the compiler have a better suggestion?

warning: value assigned to `result` is never read
  --> src/lib.rs:12:13
   |
12 |     let mut result: u8 = 0;
   |             ^^^^^^
   |
   = note: `#[warn(unused_assignments)]` on by default
   = help: maybe it is overwritten before being read?
warning: 1 warning emitted

If we remove the lines that override the default value of result with itself (result = 0;) from the code above, then this code compiles without warnings, because with that change the compiler can determine that under some conditions, the Ok() statement returns that original default value of result.

Alternatively, and in this case preferably because each condition sets the variable but only once, we can just use an immutable variable.

pub fn run(config: WinkConfig) -> Result<u8, Box<dyn std::error::Error>> {
    // categories contain lists of invocables that map command codes to commands
    let category_list = InvocableCategoryList::get();
    let result: u8;

    //TODO: convert to Err()
    if !config.help_msg.is_empty() {
        help(
            &config.help_msg.to_string(),
            config,
            category_list.categories,
        );

        result = 1;
    } else if !wsl::is_windows_or_wsl() {
        help("Runs only under Windows and Windows Subsystem for Linux (WSL). Define WSL_DISTRO_NAME environment variable to override.", config, category_list.categories);
        result = 2;
    } else if config.pretty_print && !config.export {
        help("-p invalid without -e", config, category_list.categories);
        result = 3;
    } else if config.command_code.is_empty() && !(config.export || config.dry_run) {
        help("No command specified", config, category_list.categories);
        result = 4;
    } else if let Some(invocable) = category_list.get_invocable(&config.command_code) {
        if config.export {
            if config.pretty_print {
                println!("{}", serde_json::to_string_pretty(&invocable).unwrap());
            } else {
                println!("{}", serde_json::to_string(&invocable).unwrap());
            }
        }

        Invoker {}.invoke(invocable, config.dry_run, config.verbose, config.cmd_args);
        result = 0;
    } else if config.export && config.command_code.is_empty() {
        if config.pretty_print {
            println!("{}", serde_json::to_string_pretty(&category_list).unwrap());
        } else {
            println!("{}", serde_json::to_string(&category_list).unwrap());
        }

        result = 0;
    } else if (config.command_code.is_empty() || !config.export) && config.dry_run {
        result = 0;
    } else {
        help(
            &format!("Command not recognized: {0}", config.command_code),
            config,
            category_list.categories,
        );
        result = 5;
    }

    Ok(result)
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: