Rust 1.34 includes stabilzation of the (long-overdue) TryFrom trait. It performs type conversions similar to the From trait, but may fail.

We’d like to use TryFrom while still supporting earlier toolchains where it may not be available. Essentially, we need conditional compilation based on the version of Rust. This currently isn’t exposed as a variable available to build scripts, although this may change in the future (see cargo issues 2903 and 4408). In the meantime, there’s two popular crates providing this functionality:

We’ll use version_check to expose functionality based on the version of Rust.

Conditional Compilation

In Cargo.toml:

[build-dependencies]
version_check = "0.1"

In build.rs:

// Check if version of rustc is >= 1.34
match version_check::is_min_version("1.34.0") {
    Some((true, _version)) => println!("cargo:rustc-cfg=try_from"),
    _ => {}
}

println!("cargo:rustc-cfg=try_from") is interpreted by cargo and will be passed to the rustc compiler as --cfg try_from.

Then, in lib.rs (or elsewhere in the crate):

/// The error type returned when unable to convert an integer to an enum value.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg(try_from)]
pub struct EnumFromIntError(pub i32);

impl nng_stat_type_enum {
    /// Converts value returned by [nng_stat_type](https://nanomsg.github.io/nng/man/v1.1.0/nng_stat_type.3) into `nng_stat_type_enum`.
    pub fn try_convert_from(value: i32) -> Option<Self> {
        use crate::nng_stat_type_enum::*;
        match value {
            value if value == NNG_STAT_SCOPE as i32 => Some(NNG_STAT_SCOPE),
            //...
            _ => None,
        }
    }
}

#[cfg(try_from)]
impl TryFrom<i32> for nng_stat_type_enum {
    type Error = EnumFromIntError;
    fn try_from(value: i32) -> Result<Self, Self::Error> {
        nng_stat_type_enum::try_convert_from(value).ok_or(EnumFromIntError(value))
    }
}

nng_stat_type_enum::try_convert_from() can be used with any version of Rust.

#[cfg(try_from)] is a conditional compilation attribute that enables source whenever --cfg try_from is specified (i.e. in this case it’s Rust 1.34+). This is pretty similar to #if/#ifdef of C/C++ preprocessor.

Note the similarity to cargo “features”:

  “Plain” cfg Feature
Cargo option   cargo build --features try_from
Cargo.toml   package_name = { version = "1.1.1", features = ["try_from"] }
Build script println!("cargo:rustc-cfg=try_from") println!("cargo:rustc-cfg=feature=\"try_from\"")
rustc option --cfg try_from --cfg 'feature="try_from"'
Source attribute #[cfg(try_from)] #[cfg(feature = "try_from")]

TryFrom in Action

An example using TryFrom in a i32 -> nng_stat_type_enum method:

/// See [nng_stat_type](https://nanomsg.github.io/nng/man/v1.1.0/nng_stat_type.3).
pub fn stat_type(&self) -> result::Result<nng_stat_type_enum, EnumFromIntError> {
    unsafe {
        let val: i32 = nng_stat_type(self.nng_stat());
        nng_stat_type_enum::try_from(val)
    }
}

Alternatively, using TryInto to implicitly call try_from():

/// See [nng_stat_type](https://nanomsg.github.io/nng/man/v1.1.0/nng_stat_type.3).
pub fn stat_type(&self) -> result::Result<nng_stat_type_enum, EnumFromIntError> {
    unsafe {
        let val: i32 = nng_stat_type(self.nng_stat());
        val.try_into()
    }
}

Hardly earth-shattering, perhaps even a little bit boring. But, it’s nice to have a standardized way to convert types when failure is not “exceptional”.