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
This is the standard Rust approach because Cargo.toml is a build-time file, not something Rust programs usually read directly at runtime. Cargo already knows the package metadata, so it passes useful values into the compiler environment for you.
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!("CARGO_PKG_NAME"));
println!("Version: {}", env!("CARGO_PKG_VERSION"));
println!("Description: {}", env!("CARGO_PKG_DESCRIPTION"));
}
When a value may be missing
Some metadata fields are optional in Cargo.toml. For optional values, option_env! is safer:
fn main() {
let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap_or("No homepage set");
println!("Homepage: {}", homepage);
}
Use:
env!when the variable must existoption_env!when the variable may be absent
Example: simple --version handling
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.iter().any(|arg| arg == "--version" || arg == "-V") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return;
}
println!("Run with --version to see the package version.");
}
This is a simple manual implementation. Many CLI libraries such as clap can also use Cargo metadata automatically or with minimal configuration.
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() -> &'static str {
option_env!("CARGO_PKG_DESCRIPTION").unwrap_or("No description available")
}
This is useful because some Cargo.toml fields are optional.
Pattern: integration with CLI libraries
In many projects, a library like clap is used to define the command-line interface, and Cargo metadata is passed into the CLI configuration so help and version output stay synchronized.
Pattern: build information modules
Some teams create a build_info.rs module containing package metadata constants. This keeps all version-related information in one place and makes testing or formatting easier.
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:
let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap_or("No homepage");
Expecting changes without rebuilding
If you change Cargo.toml, the binary must be rebuilt for embedded metadata to update.
Trying to parse Cargo.toml directly for simple metadata
You usually do not need to read and parse Cargo.toml at runtime just to get package version or name. Cargo already provides those values.
Assuming every Cargo field is available automatically
Only certain metadata fields are exposed through environment variables. If you need custom metadata, you may need a build script or another approach.
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 option_env!
| Macro | Returns | Use when |
|---|---|---|
env! | &'static str | The variable must exist |
option_env! | Option<&'static str> | The variable might be missing |
Manual version string vs Cargo metadata
| Approach | Pros | Cons |
|---|---|---|
| Hardcoded version in Rust code | Simple at first | Easy to forget to update |
| Read from Cargo metadata | Single source of truth | Requires understanding compile-time injection |
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: &str = env!("CARGO_PKG_NAME");
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
Important edge case
Optional fields like homepage or description may be missing, so use:
let desc = option_env!("CARGO_PKG_DESCRIPTION").unwrap_or("No description");
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?
Yes. The value is embedded into the binary during compilation.
Is this useful only for CLI tools?
No. It is also useful for logging, diagnostics, about screens, and support information.
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
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.