Parsing version from Cargo.toml

#1

I want to get version from Cargo.toml and use the value inside my app. Here is a function I wrote:

use std::fs;

pub fn get_version() -> &'static str {
    let text = fs::read_to_string("Cargo.toml").unwrap();
    for line in text.split("\n") {
        if line.starts_with("version") {
            let start = line.find('"').unwrap() + 1;
            let end = line.rfind('"').unwrap();
            let version = &line[start..end];
            return Box::leak(version.to_string().into_boxed_str());
        }
    }

    panic!("failed parsing version from Cagro.toml");
}

Writting the function, I had some questions. So I’d like to get help from others.

  • How can I replace for and if nested blocks with iterator methods?
  • To return value as static lifetime, I used Box::leak(version.to_string().into_boxed_str()) which is too complicated ( &str -> String -> Box<str> just for avoiding compile errors). How can I return &'static str or &'a str simply without getting cannot return value referencing local variable ... error in this case?
  • Can I make the code simpler? (ex: parsing the version value)

p.s.

  • I asked the same question on code review, and a user dropped me a comment that getting the version in Cargo is easily done by env!. But apart from it, I’d like to get the code reviewed.
  • This is my first post on rust community. Nice to meet you all!
#2

Okay, to address you first issue:

use std::fs;

pub fn get_version() -> &'static str {
    let text = fs::read_to_string("Cargo.toml").unwrap();
    if let Some(line) = text.split("\n").filter(|x| x.starts_with("version") {
        let start = line.find('"').unwrap() + 1;
        let end = line.rfind('"').unwrap();
        let version = &line[start..end];
        return Box::leak(version.to_string().into_boxed_str());
    } else {
        panic!("failed parsing version from Cagro.toml");
    }
}

This uses a more concise way to check for the version, since there should only be one version. Next, you have two options for returning the &'static str: return an owned String or use this:

const TOML: &'static str = include_str!("Cargo.toml");
pub fn get_version() -> &'static str {
    if let Some(line) = TOML.split("\n").filter(|x| x.starts_with("version") {
        let start = line.find('"').unwrap() + 1;
        let end = line.rfind('"').unwrap();
        &line[start..end]
    } else {
        panic!("failed parsing version from Cagro.toml");
    }
}
1 Like
#3

These solutions assume that [package] version will be the first version in the file, as opposed to some [dependencies.foo] version, and also that it’s formatted normally at the start of a new line. That’s probably true, but not necessarily, so if you need to be any more robust I would use the toml crate.

But for getting your own version, env!("CARGO_PKG_VERSION") is really the best method.

3 Likes
#4

Hi,

If your app is a CLI tool, you can eventually use the crate_version! macro from clap.

1 Like
#5

Aside from the issue of getting the version from Cargo.toml, what you’ve written will retrieve the value at runtime, which won’t work if you give someone a binary (they may not even have a Cargo.toml) or if you update the Cargo.toml file without rebuilding immediately. So you’ll possibly want to look at retrieving the version at compile time instead.

1 Like