Rust TryFrom and Conditional Compilation
Tags: rust
Published: 2019-04-23 08:43:34

---
title: Rust TryFrom and Conditional Compilation
published: true
tags: rust
canonical_url: 
---

[Rust 1.34](https://blog.rust-lang.org/2019/04/11/Rust-1.34.0.html) includes stabilization of the (long-overdue) [TryFrom](https://doc.rust-lang.org/std/convert/trait.TryFrom.html) trait. It performs type conversions similar to the [From trait](https://doc.rust-lang.org/std/convert/trait.From.html), 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](https://doc.rust-lang.org/cargo/reference/environment-variables.html), although this may change in the future (see cargo issues [2903](https://github.com/rust-lang/cargo/issues/2903) and [4408](https://github.com/rust-lang/cargo/issues/4408)). In the meantime, there’s two popular crates providing this functionality:

- [version\_check](https://crates.io/crates/version_check) ([Github](https://github.com/SergioBenitez/version_check))
- [rustc\_verison](https://crates.io/crates/rustc_version)

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

## Conditional Compilation

In `Cargo.toml`:

```toml
[build-dependencies]
version_check = "0.1"
```

In `build.rs`:

```rust
// 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](https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script) and will be passed to the rustc compiler as `--cfg try_from`.

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

```rust
/// 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](https://doc.rust-lang.org/reference/conditional-compilation.html) 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”](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section):

|   | “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:

```rust
/// 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](https://doc.rust-lang.org/std/convert/trait.TryInto.html) to implicitly call `try_from()`:

```rust
/// 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”.