Rust: Beyond Memory Safety – A Developer's Perspective on Speed, Simplicity, and Productivity

This website uses cookies
We use Cookies to ensure better performance, recognize your repeat visits and preferences, as well as to measure the effectiveness of campaigns and analyze traffic. For these reasons, we may share your site usage data with our analytics partners. Please, view our Cookie Policy to learn more about Cookies. By clicking «Allow all cookies», you consent to the use of ALL Cookies unless you disable them at any time.
A Command-Line Interface (CLI) is a text-based user interface that allows users to interact with a computer system by typing commands into a terminal or console. Unlike graphical user interfaces (GUIs), which rely on visual elements such as buttons and menus, a CLI provides a direct method for executing tasks using text-based commands. This method of interaction has been fundamental in computing since the early days and remains a critical tool for developers and system administrators today.
CLI tools offer several key advantages that make them indispensable in software development and system administration:
Efficiency and Speed – CLI commands are often faster than GUI operations, allowing users to execute complex tasks quickly with just a few keystrokes.
Automation and Scripting – CLI tools can be easily automated using scripts (such as Bash, PowerShell, or Python), reducing repetitive tasks and improving workflow efficiency.
Remote Access – CLI tools enable remote systems management via SSH (Secure Shell), making them essential for server administration.
Resource Efficiency – Unlike GUI-based applications, CLI tools consume fewer system resources, making them ideal for low-power or headless environments (e.g., cloud servers, embedded systems).
Precision and Control – CLI commands allow users to perform precise operations, often with more flexibility than their GUI counterparts.
Extensibility – Many CLI tools offer extensive customization and integration options through configuration files, plugins, and command chaining.
The power of CLI tools is evident across various fields, particularly in:
DevOps & System Administration – Managing servers, deploying applications, configuring cloud services, and monitoring system performance using tools like Docker, Kubernetes, Terraform, and Ansible.
Automation & Scripting – Automating repetitive tasks such as file manipulation, backups, and deployments using Bash, PowerShell, or Python scripts.
Data Analysis & Processing – Handling large datasets, transforming files, and performing statistical analysis using command-line tools like awk, sed, grep, jq, and pandas in Python.
Software Development – Building, testing, and managing code repositories with tools like Git, npm, yarn, and Make.
Networking & Security – Troubleshooting network issues, analyzing logs, and securing systems using tools like netstat, tcpdump, nmap, and OpenSSL.
CLI tools provide developers and system administrators with powerful capabilities to streamline workflows, improve efficiency, and automate complex tasks. In the following sections, we will explore how to use CLI tools effectively and integrate them into daily development processes.
Rust’s unique ownership model prevents memory leaks and segmentation faults by enforcing strict memory safety at compile time. This eliminates common issues such as null pointer dereferencing and buffer overflows, ensuring robust and reliable CLI applications.
Rust compiles to native machine code, allowing CLI applications to run with maximum efficiency. Unlike interpreted languages, Rust provides performance comparable to C and C++, making it an excellent choice for high-speed command-line utilities.
Rust enables developers to write CLI tools that work seamlessly across different operating systems, including Windows, macOS, and Linux. The Rust standard library and ecosystem provide cross-platform abstractions, ensuring smooth compatibility.
Rust features a modern, expressive syntax that enhances readability and maintainability. Libraries like Clap and StructOpt make argument parsing easy, providing developers with powerful options for handling user inputs in CLI applications.
Rust boasts a thriving ecosystem with tools like:
Cargo – A built-in package manager that simplifies dependency management and builds.
Clap & StructOpt – Libraries for intuitive command-line argument parsing.
Serde – A robust framework for serializing and deserializing structured data, essential for configuration handling in CLI applications.
By leveraging these features, Rust enables developers to build secure, efficient, and maintainable CLI tools that can scale across diverse use cases. In the next sections, we will dive into setting up a Rust CLI project and exploring practical implementation strategies.
Rust is officially distributed through Rustup, a tool that simplifies the installation and management of Rust versions. It provides seamless updates and the ability to install different Rust toolchains.
To install Rust using Rustup, open a terminal and run the following command:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Follow the on-screen instructions to complete the installation. Rustup will set up Rust, Cargo (the Rust package manager), and other necessary tools.
Once the installation is complete, verify that Rust has been installed correctly by checking the version:
rustc --version
This should output the installed Rust version. You can also check Cargo’s version with:
cargo --version
Rust is successfully installed and ready for development if both commands return valid versions.
To create a new CLI project in Rust, use Cargo, Rust’s built-in package manager and project generator. Run the following command in your terminal:
cargo new my_cli_tool --bin
This command creates a new binary project (--bin
), setting up the necessary structure for a Rust application.
After running the command, you will see the following directory structure:
my_cli_tool/
├── Cargo.toml # Project manifest file (dependencies and metadata)
├── src/
│ ├── main.rs # Entry point of the application
Cargo.toml – Defines dependencies and metadata about the project.
src/main.rs – The main entry point for the Rust application.
To enhance functionality, add the following dependencies to your Cargo.toml
file:
[dependencies]
clap = "4.0" # Command-line argument parsing
structopt = "0.3" # High-level argument parsing
serde = "1.0" # Serialization/deserialization
log = "0.4" # Logging utility
Run cargo build
to install the dependencies and compile the project.
By setting up these dependencies, your Rust CLI tool will be well-equipped for robust argument parsing, serialization, and logging functionalities.
Clap is a powerful Rust library designed for command-line argument parsing. It simplifies handling user input, making CLI tools more intuitive and user-friendly.
Automatic Generation of Help and Version Information – Clap automatically provides --help
and --version
flags without additional code.
Flexible Argument Parsing – Supports subcommands, default values, and validation.
Ease of Use – Provides a builder-style API and derives macros for simple and complex CLIs.
The application will accept command-line arguments, process them, and perform actions based on the provided input. We will use Rust's clap
crate, which is a popular library for parsing command-line arguments.
The first step in creating a CLI application is to define the command-line arguments that the application will accept. These arguments can include options, flags, and positional parameters. For example, let's say we want to create a CLI application that greets the user. The application will accept the following arguments:
--name
or -n
: A required argument that specifies the name of the user.
--count
or -c
: An optional argument that specifies the number of times the greeting should be repeated (default is 1).
Command::new()
to Create an InterfaceTo define these arguments in Rust, we will use the clap
crate. The Command::new()
function is used to create a new command-line interface. Here's how we can define the arguments:
use clap::{Command, Arg};
fn main() {
let matches = Command::new("greet")
.version("1.0")
.author("Your Name <your.email@example.com>")
.about("A simple greeting application")
.arg(
Arg::new("name")
.short('n')
.long("name")
.value_name("NAME")
.about("Specifies the name of the user")
.required(true),
)
.arg(
Arg::new("count")
.short('c')
.long("count")
.value_name("COUNT")
.about("Specifies the number of times to greet the user")
.default_value("1"),
)
.get_matches();
}
In this code, we define a new command called greet
with two arguments: name
and count
. The name
argument is required, while the count
argument has a default value of 1
.
Once the command-line arguments are defined, the next step is to read and process them. The get_matches()
method is used to parse the command-line input and return a clap::ArgMatches
object, which can be used to retrieve the values of the arguments.
let name = matches.value_of("name").unwrap();
let count: u32 = matches.value_of("count").unwrap().parse().unwrap();
for _ in 0..count {
println!("Hello, {}!", name);
}
In this code, we retrieve the values of the name
count
arguments using the value_of()
method. The count
argument is parsed into an integer, and then we use a loop to print the greeting message the specified number of times.
To compile and run the application, use the following commands in your terminal:
$ cargo build
$ ./target/debug/greet --name Alice --count 3
This will output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
You can also run the application with the default value for count
:
$ ./target/debug/greet --name Bob
This will output:
Hello, Bob!
If you run the application without providing the required name
argument, clap
will automatically display an error message and the usage information.
$ ./target/debug/greet
Output:
error: The following required arguments were not provided:
--name <NAME>
USAGE:
greet --name <NAME> [--count <COUNT>]
For more information try --help
This concludes the development of a simple CLI application in Rust. By following these steps, you can create more complex CLI applications with multiple commands, subcommands, and advanced argument parsing features.
The StructOpt
crate is a powerful library that simplifies the process of defining and parsing command-line arguments. It leverages Rust's procedural macros to automatically generate argument parsing logic, reducing the amount of manual code required.
Macros for Intuitive Argument Definition: StructOpt
uses Rust's #[derive]
attribute to automatically generate argument parsing logic. This makes the code more concise and easier to read.
Intuitive Argument Definition: Instead of manually defining arguments using clap::Arg
, you can define them as fields in a struct, making the code more intuitive and maintainable.
Automatic Generation of --help
and --version
: StructOpt
automatically generates help and version flags, saving developers from writing additional boilerplate code.
To use StructOpt
, you define a struct that represents the command-line arguments. Each field in the struct corresponds to a command-line argument, and you can use attributes to specify additional details like short/long flags, default values, and help text.
Here’s an example of defining a simple CLI application using StructOpt
:
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "greet", about = "A simple greeting application")]
struct Cli {
#[structopt(short, long, help = "Specifies the name of the user")]
name: String,
#[structopt(short, long, default_value = "1", help = "Specifies the number of times to greet the user")]
count: u32,
}
fn main() {
let args = Cli::from_args();
for _ in 0..args.count {
println!("Hello, {}!", args.name);
}
}
In this example:
The Cli
struct defines two fields: name
and count
.
The #[structopt]
attribute is used to specify short/long flags, help text, and default values.
The from_args()
method automatically parses the command-line arguments into the Cli
struct.
--help
and --version
When you run the application with the --help
flag, StructOpt
automatically generates a help message based on the struct definition:
$ ./target/debug/greet --help
Output:
greet 1.0
A simple greeting application
USAGE:
greet --name <NAME> [--count <COUNT>]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-n, --name <NAME> Specifies the name of the user
-c, --count <COUNT> Specifies the number of times to greet the user [default: 1]
Similarly, the --version
flag displays the application's version.
For more complex CLI applications, you may need to support multiple commands (subcommands). StructOpt
makes it easy to define and parse subcommands using Rust's enum
type.
enum
and derive(StructOpt)
To implement subcommands, you define an enum
where each variant represents a different command. Each variant can have its own set of arguments, defined as a struct.
Here’s an example of a CLI application with two subcommands: greet
and sum
:
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "cli", about = "A CLI application with multiple commands")]
enum Command {
#[structopt(name = "greet", about = "Greet the user")]
Greet {
#[structopt(short, long, help = "Specifies the name of the user")]
name: String,
#[structopt(short, long, default_value = "1", help = "Specifies the number of times to greet the user")]
count: u32,
},
#[structopt(name = "sum", about = "Calculate the sum of two numbers")]
Sum {
#[structopt(help = "The first number")]
a: f64,
#[structopt(help = "The second number")]
b: f64,
},
}
fn main() {
let args = Command::from_args();
match args {
Command::Greet { name, count } => {
for _ in 0..count {
println!("Hello, {}!", name);
}
}
Command::Sum { a, b } => {
println!("The sum of {} and {} is {}", a, b, a + b);
}
}
}
In this example:
The Command
enum defines two subcommands: greet
and sum
.
Each subcommand has its own set of arguments, defined as fields in the corresponding struct.
greet
, sum
)When you run the application, you can specify the subcommand and its arguments:
$ ./target/debug/cli greet --name Alice --count 3
Output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
Or, to use the sum
subcommand:
$ ./target/debug/cli sum 10.5 20.3
Output:
The sum of 10.5 and 20.3 is 30.8
To test the application, you can run it with different combinations of subcommands and arguments. For example:
Test the greet
subcommand with default count:
$ ./target/debug/cli greet --name Bob
Output:
Hello, Bob!
Test the sum
subcommand with negative numbers:
$ ./target/debug/cli sum -5.5 3.2
Output:
The sum of -5.5 and 3.2 is -2.3
Test the --help
flag for the greet
subcommand:
$ ./target/debug/cli greet --help
Output:
cli-greet
Greet the user
USAGE:
cli greet --name <NAME> [--count <COUNT>]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-n, --name <NAME> Specifies the name of the user
-c, --count <COUNT> Specifies the number of times to greet the user [default: 1]
By using StructOpt
and subcommands, you can create powerful and flexible CLI applications with minimal effort. This approach is particularly useful for applications that require multiple commands or complex argument structures.
Once your CLI application is developed and tested, the next step is to deploy and distribute it to users. This section covers optimizing the application, packaging it for distribution, and setting up continuous integration and deployment (CI/CD) pipelines.
Before distributing your CLI application, it’s important to optimize it for performance and reduce the size of the final binary.
cargo build --release
)Rust provides two main build profiles: debug
and release
. The debug
profile is used during development, while the release
profile is optimized for performance and is suitable for distribution.
To compile your application in release mode, use the following command:
$ cargo build --release
The resulting binary will be located in the target/release
directory. Release mode enables optimizations such as:
Code Optimization: The Rust compiler applies various optimizations to improve performance.
Stripping Debug Symbols: Debug information is removed, reducing the binary size.
To further reduce the size of the binary, you can use the following techniques:
Strip Debug Symbols:
Use the strip
command to remove debug symbols from the binary:
$ strip target/release/my_app
Optimize for Size:
Add the following to your Cargo.toml
to optimize the binary for size:
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Enable link-time optimization
Remove Unused Dependencies:
Use the cargo udeps
tool to identify and remove unused dependencies:
$ cargo install cargo-udeps
$ cargo udeps
Once your application is optimized, you can package and distribute it to users. There are several ways to distribute a Rust CLI application.
tar.gz
for DistributionYou can create a compressed archive (e.g., tar.gz
) containing the binary and any necessary files. This is useful for distributing the application to users who may not have Rust installed.
Build the application in release mode:
$ cargo build --release
Create a tar.gz
archive:
$ tar -czvf my_app-v1.0.0-x86_64-unknown-linux-gnu.tar.gz -C target/release my_app
Distribute the archive to users, who can extract and run the binary:
$ tar -xzvf my_app-v1.0.0-x86_64-unknown-linux-gnu.tar.gz
$ ./my_app
crates.io
crates.io
, the official Rust package registry.Add metadata to your Cargo.toml
:
[package]
name = "my_app"
version = "1.0.0"
authors = ["Your Name <your.email@example.com>"]
description = "A simple CLI application"
license = "MIT"
repository = "https://github.com/yourusername/my_app"
Publish the crate:
$ cargo publish
cargo install
Users can install your CLI tool directly from crates.io
using cargo install
:
$ cargo install my_app
This downloads, compiles, and installs the binary in the user’s ~/.cargo/bin
directory.
CI/CD for CLI Applications
Setting up a CI/CD pipeline ensures that your application is automatically built, tested, and deployed whenever changes are made to the codebase.
Setting Up Automatic Builds with GitHub Actions
GitHub Actions is a popular CI/CD tool that integrates seamlessly with GitHub repositories. Here’s how to set up a basic workflow for your Rust CLI application:
Create a .github/workflows/ci.yml
file in your repository:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build
run: cargo build --release
- name: Run tests
run: cargo test
Push the workflow file to your repository. GitHub Actions will automatically run the workflow on every push or pull request.
You can extend the GitHub Actions workflow to automatically publish new versions of your CLI tool to crates.io
or create release artifacts.
Add a publish
job to the workflow:
publish:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
Add your crates.io
API token as a secret in your GitHub repository settings.
Ensure that all tests pass before deploying a new version. The cargo test
command is included in the workflow to run your test suite automatically.
Building CLI applications in Rust is a rewarding experience, thanks to the language’s performance, safety, and rich ecosystem. By following the steps outlined in this guide, you can create powerful and user-friendly tools that are easy to maintain and distribute.
As you continue to develop your CLI application, consider exploring additional features, such as interactive interfaces, API integrations, and advanced testing techniques. The possibilities are endless, and Rust provides the tools you need to bring your ideas to life.
At Technorely, we are a highly competent and experienced team of developers and blockchain experts. If you have any questions or need assistance with your project, feel free to reach out to me directly. We specialize in blockchain technology and are ready to help with projects of any complexity. Whether you need guidance, consulting, or full-scale development, we’ve got you covered.
Simply drop me a message or fill out the contact form on our website, and we’ll be happy to assist you in bringing your vision to reality.
George Burlakov
8 min
LEARN MOREDmitriy Malets
10 min
LEARN MOREGeorge Burlakov
7 min
LEARN MOREGeorge Burlakov
8 min
LEARN MORE