Question

Accessing Cargo Package Metadata in Rust

rustrust-cargo

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_NAME
  • CARGO_PKG_VERSION
  • CARGO_PKG_VERSION_MAJOR
  • CARGO_PKG_VERSION_MINOR
  • CARGO_PKG_VERSION_PATCH
  • CARGO_PKG_AUTHORS
  • CARGO_PKG_DESCRIPTION
  • CARGO_PKG_HOMEPAGE
  • CARGO_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_NAME
  • CARGO_PKG_VERSION
  • CARGO_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.

Take Quiz

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 exist
  • option_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:

  1. Cargo reads Cargo.toml.
  2. It sees package metadata such as name and version.
  3. Cargo sets compile-time environment variables like:
    • CARGO_PKG_NAME
    • CARGO_PKG_VERSION
  4. The Rust compiler expands env!("CARGO_PKG_NAME") into a string literal.
  5. The Rust compiler expands env!("CARGO_PKG_VERSION") into a string literal.
  6. The compiled binary now contains those values directly.
  7. 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.

Take Quiz

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

ApproachWhen it happensBest forNotes
env!("CARGO_PKG_VERSION")Compile timeRequired package metadataFails to compile if missing
option_env!("CARGO_PKG_HOMEPAGE")Compile timeOptional metadataReturns Option<&'static str>
std::env::var("...")RuntimeReal runtime environment variablesNot the usual way to read Cargo package metadata
Parsing Cargo.toml manuallyRuntimeRare special casesMore complex and usually unnecessary

env! vs option_env!

MacroReturnsUse when
env!&'static strThe variable must exist
option_env!Option<&'static str>The variable might be missing

Manual version string vs Cargo metadata

ApproachProsCons
Hardcoded version in Rust codeSimple at firstEasy to forget to update
Read from Cargo metadataSingle source of truthRequires understanding compile-time injection
Take Quiz

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 exist
  • option_env! returns None if 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.

Take Quiz

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 --version and -V command-line flags.
  • Print an optional package description with a fallback if it is not set.
  • Exit early after printing version information.
Take Quiz

Keep learning

Related questions

View more