Question
How can a Rust program access metadata from its Cargo package?
For example, how can Rust code read values such as the package version from Cargo.toml? A common use case is building a command-line tool with a standard --version flag and wanting that flag to display the version defined in Cargo.toml, so the version does not need to be maintained in two places.
More generally, what is the standard way for Rust code inside a package to access Cargo-provided metadata?
Short Answer
By the end of this page, you will understand how Rust programs can access Cargo package metadata such as name, version, description, and authors using compile-time environment macros like env! and option_env!. You will also see how this is commonly used in CLI applications, how the values are injected by Cargo, and how to avoid common mistakes.
Concept
Cargo exposes selected package metadata from Cargo.toml to your Rust code through environment variables set at compile time. Rust code can read these values using macros such as:
env!("CARGO_PKG_VERSION")
This means the value is embedded into the compiled binary when the project is built.
Common Cargo package metadata variables include:
CARGO_PKG_NAMECARGO_PKG_VERSIONCARGO_PKG_VERSION_MAJORCARGO_PKG_VERSION_MINORCARGO_PKG_VERSION_PATCHCARGO_PKG_AUTHORSCARGO_PKG_DESCRIPTIONCARGO_PKG_HOMEPAGECARGO_PKG_REPOSITORY
Why this matters:
- You avoid duplicating metadata in code and
Cargo.toml - CLI tools can print accurate version information
- Build output stays consistent with package configuration
- Metadata is available without manually parsing
Cargo.toml
Mental Model
Think of Cargo as a build assistant that fills out a form before your program is compiled.
Your Cargo.toml is the source form. During compilation, Cargo copies some fields from that form into labeled boxes such as:
CARGO_PKG_NAMECARGO_PKG_VERSIONCARGO_PKG_DESCRIPTION
Then your Rust code can say, “Put the contents of this box directly into my program.”
So instead of opening and reading Cargo.toml while the program runs, the values are baked into the binary ahead of time.
Syntax and Examples
The most common tool is the env! macro.
Basic syntax
const VERSION: &str = env!("CARGO_PKG_VERSION");
This inserts the package version as a string slice at compile time.
Example: print package version
fn main() {
println!("Version: {}", env!("CARGO_PKG_VERSION"));
}
If your Cargo.toml contains:
[package]
name = "my_tool"
version = "1.2.3"
The program prints:
Version: 1.2.3
Example: print multiple metadata fields
fn main() {
println!("Name: {}", env!());
(, ());
(, ());
}
Step by Step Execution
Consider this example:
fn main() {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
println!("{} {}", name, version);
}
Step by step:
- Cargo reads
Cargo.toml. - It sees package metadata such as
nameandversion. - Cargo sets compile-time environment variables like:
CARGO_PKG_NAMECARGO_PKG_VERSION
- The Rust compiler expands
env!("CARGO_PKG_NAME")into a string literal. - The Rust compiler expands
env!("CARGO_PKG_VERSION")into a string literal. - The compiled binary now contains those values directly.
- When the program runs, it prints something like:
my_tool 1.2.3
Important detail: the program is not reading Cargo.toml at runtime. The values were inserted earlier, during compilation.
Real World Use Cases
Here are common places where Cargo package metadata is useful:
Command-line tools
Display version and name:
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
Useful for:
--version- startup banners
- debug output
About text in desktop or terminal apps
You can show:
- app name
- version
- description
- homepage
Logging and diagnostics
When an app starts, it may log its version for support and debugging:
println!("Starting {} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
Build identification
Teams often include package version in crash reports, telemetry, or support logs.
Generated help text
CLI frameworks often display metadata pulled from Cargo so users see accurate package information without duplicated strings.
Real Codebase Usage
In real Rust projects, developers usually use Cargo metadata in a few standard patterns.
Pattern: constants for reuse
const APP_NAME: &str = env!("CARGO_PKG_NAME");
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
This avoids repeating the macro throughout the codebase.
Pattern: guard clause for --version
use std::env;
fn main() {
if env::args().any(|arg| arg == "--version" || arg == "-V") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return;
}
// normal execution continues here
}
This is a clean example of an early return.
Pattern: optional metadata with fallback
fn app_description() & {
().()
}
Common Mistakes
Using runtime environment APIs instead of compile-time macros
Beginners sometimes try this:
use std::env;
fn main() {
let version = env::var("CARGO_PKG_VERSION").unwrap();
println!("{}", version);
}
This may fail at runtime because CARGO_PKG_VERSION is typically meant for compile time during Cargo builds, not guaranteed as a runtime environment variable.
Prefer:
fn main() {
println!("{}", env!("CARGO_PKG_VERSION"));
}
Using env! for optional metadata
This can fail to compile if the variable is not set:
let homepage = env!("CARGO_PKG_HOMEPAGE");
Safer version:
= ().();
Comparisons
| Approach | When it happens | Best for | Notes |
|---|---|---|---|
env!("CARGO_PKG_VERSION") | Compile time | Required package metadata | Fails to compile if missing |
option_env!("CARGO_PKG_HOMEPAGE") | Compile time | Optional metadata | Returns Option<&'static str> |
std::env::var("...") | Runtime | Real runtime environment variables | Not the usual way to read Cargo package metadata |
Parsing Cargo.toml manually | Runtime | Rare special cases | More complex and usually unnecessary |
env! vs
Cheat Sheet
env!("CARGO_PKG_NAME")
env!("CARGO_PKG_VERSION")
env!("CARGO_PKG_VERSION_MAJOR")
env!("CARGO_PKG_VERSION_MINOR")
env!("CARGO_PKG_VERSION_PATCH")
option_env!("CARGO_PKG_DESCRIPTION")
option_env!("CARGO_PKG_HOMEPAGE")
option_env!("CARGO_PKG_REPOSITORY")
Rules
- Cargo provides package metadata as compile-time environment variables.
- Use
env!for required values. - Use
option_env!for optional values. - These values are embedded into the binary at compile time.
- Rebuild after changing
Cargo.toml.
Common example
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
Good default pattern
const APP_NAME: & = ();
APP_VERSION: & = ();
FAQ
How do I get the version from Cargo.toml in Rust?
Use:
env!("CARGO_PKG_VERSION")
This gives you the package version at compile time.
Can I access the package name too?
Yes:
env!("CARGO_PKG_NAME")
Should I read Cargo.toml directly at runtime?
Usually no. For standard package metadata, Cargo already exposes the values you need.
What is the difference between env! and option_env!?
env!requires the variable to existoption_env!returnsNoneif it does not exist
Why does std::env::var("CARGO_PKG_VERSION") not work reliably?
Because Cargo package metadata is typically intended for compile-time use, not guaranteed as a runtime environment variable.
Do I need to rebuild after changing the version in Cargo.toml?
Mini Project
Description
Build a small Rust command-line program that supports a --version flag and prints package information from Cargo metadata. This demonstrates how to keep your CLI output synchronized with Cargo.toml without duplicating version strings in your Rust source code.
Goal
Create a CLI program that prints its package name, version, and optional description using Cargo-provided metadata.
Requirements
- Read the package name from Cargo metadata.
- Read the package version from Cargo metadata.
- Support
--versionand-Vcommand-line flags. - Print an optional package description with a fallback if it is not set.
- Exit early after printing version information.
Keep learning
Related questions
Default Function Arguments in Rust: What to Use Instead
Learn how Rust handles default function arguments, why they are not supported, and practical patterns to achieve similar behavior.
Fixing Rust "linker 'cc' not found" on Debian in WSL
Learn why Rust shows "linker 'cc' not found" on Debian in WSL and how to fix it by installing the required C build tools.
Handling Errors While Mapping Iterators in Rust
Learn how to stop iterator processing and return errors in Rust using collect, Result, and iterator patterns.