How a Rust program reads its metadata from Cargo.toml
\n\nHave you ever written a CLI utility in Rust and wanted it to output not "0.1.0" but exactly the version specified in Cargo.toml when run with the --version flag? Or maybe you needed to log the crate name and its description — but pulling these strings from code was inconvenient and error-prone?
This question is one of the most popular on Stack Overflow (thousands of views!). And for good reason: it would seem that reading your own config couldn't be simpler. But Rust has its pitfalls, and more importantly, several elegant solutions.
\n\nIn this article, we'll cover three main approaches to getting Cargo package metadata directly from code: from the simple env!() macro to the flexible build.rs. You'll learn how to correctly read the version, crate name, description, and how to avoid common mistakes.
Why is this needed? Three typical scenarios
\n\nBefore diving into the code, let's understand why you'd want to access package metadata. Here are three real-world cases:
\n\n- \n
- CLI utilities: the
myapp --versioncommand should output the version fromCargo.toml, not hardcoded. \n - Logs and telemetry: at application startup, it's useful to log the crate name and version — this simplifies debugging. \n
- Automatic documentation generation: you can insert the crate description into a help message or a dynamically generated README. \n
Now that we understand the "why", let's move on to the "how".
\n\nMethod #1: The env!() macro — simplest and most popular
\n\nThe most straightforward way is to use the built-in env!() macro. It allows you to read environment variables that Cargo automatically sets during compilation. Here is a list of the most useful ones:
- \n
CARGO_PKG_NAME— package name (e.g., "my_cool_app") \n CARGO_PKG_VERSION— version (e.g., "0.2.1") \n CARGO_PKG_DESCRIPTION— description from thedescriptionfield inCargo.toml\n CARGO_PKG_AUTHORS— list of authors, separated by colons \n CARGO_PKG_LICENSE— license \n CARGO_PKG_REPOSITORY— repository URL \n
Example usage:
\n\nfn main() {\n let name = env!("CARGO_PKG_NAME");\n let version = env!("CARGO_PKG_VERSION");\n\n println!("{} v{}", name, version);\n // Output: my_cool_app v0.2.1\n}\n\n\nImportant: env!() is a compile-time macro. If the environment variable is not set, the program won't even compile! This is a plus (type safety) and a minus (you can't fall back to hardcoded values without additional tricks).
If you only need the version for debugging, you can use option_env!(), which returns Option<&str>:
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");\nprintln!("Version: {}", version);\n\n\nThis method is ideal for 90% of cases. It requires no additional dependencies and works out of the box.
\n\nMethod #2: include_str!() + manual parsing — for maximum control
\n\nSometimes you need not just to read a single field, but to get structured data from Cargo.toml. For example, all dependencies or keywords. In this case, you can read the file itself as a string and parse it.
fn main() {\n let cargo_toml = include_str!("../Cargo.toml");\n // Here you could use a toml parser, but for example — simple search\n if let Some(line) = cargo_toml.lines().find(|l| l.starts_with("version =")) {\n let version = line.trim_start_matches("version = \\"").trim_end_matches('"');\n println!("Version from file: {}", version);