Analyst 18   Software Engineering and other random() subjects

Expressiveness in Rust code

After a quick attempt to build a desktop application using Tauri, I decided to learn Rust and started by using the excellent Rustlings resource.

I like to compare my solutions and experiment with different ways to solve the challenges, and I’ve documented that in my commented solutions repository:

https://github.com/dezoito/rustlings-commented-solutions

The first time I saw the language, I had the impression that it was excessively verbose, but the brief experience has changed my mind – to some extent.

For example, in exercises/conversions/try_from_into.rs, we are challenged to implement different conversion methods for a Color struct:

Conversion from Tuple to Color Struct

Without getting into too much detail, the second version uses variable destructuring and Rust’s type system to validate the data:


// My horribly verobose version:
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
    let is_valid_range = |x: i16| (0..=255).contains(&x);

    if is_valid_range(tuple.0) && is_valid_range(tuple.1) && is_valid_range(tuple.2) {
        Ok(Color {
            red: tuple.0 as u8,
            green: tuple.1 as u8,
            blue: tuple.2 as u8,
        })
    } else {
        Err(IntoColorError::IntConversion)
    }
}
// A more idiomatic approach:
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
    let (r, g, b) = tuple;
    let (red, green, blue) = (
        u8::try_from(r).or(Err(IntoColorError::IntConversion))?,
        u8::try_from(g).or(Err(IntoColorError::IntConversion))?,
        u8::try_from(b).or(Err(IntoColorError::IntConversion))?,
    );
    Ok(Color { red, green, blue })
}

The second version is shorter, more readable, but wait, things get better…

Conversion from an Array to Color Struct

// I repeat the logic for the tuple conversion
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
    let is_valid_range = |x: i16| (0..=255).contains(&x);

    if is_valid_range(arr[0]) && is_valid_range(arr[1]) && is_valid_range(arr[2]) {
        Ok(Color {
            red: arr[0] as u8,
            green: arr[1] as u8,
            blue: arr[2] as u8,
        })
    } else {
        Err(IntoColorError::IntConversion)
    }
}

// A smarter person just converts the array into a tuple and
// uses the previous implementation
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
    let [r, g, b] = arr;
    (r, g, b).try_into()
}

Conversion from an Array Slice to Color Struct

A similar pattern can be used in the other implementations (I’m omitting mine since it looks as convoluted as the previous):

// Attempts to convert from a slice, returns appropriate error
// if it fails
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
    match slice {
        [r, g, b] => Color::try_from((*r, *g, *b)),
        _ => Err(IntoColorError::BadLen)?
    }
}

In summary, my brief experience with Rust has taught me that while it may initially seem verbose, the language’s true expressiveness becomes evident when you invest the time to learn it.

comments powered by Disqus