Cross-Compilation

To compile your application for different platforms, you can use the cross tool:

# Install cross
cargo install cross

# Build for Windows
cross build --target x86_64-pc-windows-gnu --release

# Build for macOS
cross build --target x86_64-apple-darwin --release

# Build for Linux
cross build --target x86_64-unknown-linux-gnu --release

Binary Size Optimization

Rust binaries can be large, but several techniques can reduce their size:

# Cargo.toml
[profile.release]
opt-level = 'z'     # Optimize for size
lto = true          # Enable Link Time Optimization
codegen-units = 1   # Reduce parallel code generation units
panic = 'abort'     # Abort on panic
strip = true        # Strip symbols from binary

After building, you can further reduce the size using tools like upx:

upx --best --lzma target/release/myapp

Distribution via Package Managers

For wider distribution, you can publish your application to package managers:

  • Cargo: Publish to crates.io with cargo publish
  • Homebrew: Create a formula for macOS users
  • Apt/Yum/Pacman: Create packages for Linux distributions
  • Chocolatey/Scoop: Create packages for Windows users

Advanced Techniques

Let’s explore some advanced techniques for building more sophisticated CLI applications:

Interactive User Input

The dialoguer crate provides utilities for interactive user input:

use dialoguer::{Confirm, Input, MultiSelect, Password, Select};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Simple confirmation
    if Confirm::new().with_prompt("Do you want to continue?").interact()? {
        println!("Continuing...");
    } else {
        println!("Aborting.");
        return Ok(());
    }
    
    // Text input
    let name: String = Input::new()
        .with_prompt("Enter your name")
        .default("User".into())
        .interact_text()?;
    
    // Password input
    let password = Password::new()
        .with_prompt("Enter your password")
        .with_confirmation("Confirm password", "Passwords don't match")
        .interact()?;
    
    // Selection from a list
    let items = vec!["Option 1", "Option 2", "Option 3"];
    let selection = Select::new()
        .with_prompt("Select an option")
        .items(&items)
        .default(0)
        .interact()?;
    
    // Multiple selection
    let selections = MultiSelect::new()
        .with_prompt("Select multiple options")
        .items(&items)
        .interact()?;
    
    println!("Name: {}", name);
    println!("Password length: {}", password.len());
    println!("Selected option: {}", items[selection]);
    println!("Multiple selections: {:?}", selections.iter().map(|i| items[*i]).collect::<Vec<_>>());
    
    Ok(())
}

Persistent State

For applications that need to maintain state between runs, you can use the directories crate to find appropriate directories for configuration and data:

use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize)]
struct AppState {
    last_run: chrono::DateTime<chrono::Utc>,
    run_count: u32,
    last_file: Option<String>,
}

impl Default for AppState {
    fn default() -> Self {
        Self {
            last_run: chrono::Utc::now(),
            run_count: 0,
            last_file: None,
        }
    }
}

fn get_state_file() -> Option<PathBuf> {
    ProjectDirs::from("com", "example", "myapp").map(|dirs| {
        let data_dir = dirs.data_dir();
        fs::create_dir_all(data_dir).ok()?;
        Some(data_dir.join("state.json"))
    })?
}

fn load_state() -> AppState {
    if let Some(state_file) = get_state_file() {
        if let Ok(content) = fs::read_to_string(state_file) {
            if let Ok(state) = serde_json::from_str(&content) {
                return state;
            }
        }
    }
    AppState::default()
}

fn save_state(state: &AppState) -> Result<(), Box<dyn std::error::Error>> {
    if let Some(state_file) = get_state_file() {
        let content = serde_json::to_string_pretty(state)?;
        fs::write(state_file, content)?;
    }
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load state
    let mut state = load_state();
    
    // Update state
    state.run_count += 1;
    state.last_run = chrono::Utc::now();
    state.last_file = Some("example.txt".to_string());
    
    println!("Run count: {}", state.run_count);
    println!("Last run: {}", state.last_run);
    
    // Save state
    save_state(&state)?;
    
    Ok(())
}