Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the first specific enum variant in an iterator and transform it

I have an enum with different variants and I want to find the first variant that matches then transform it by either returning the variant value or mapping it to something else.

In Scala, I'd use case classes to do something like:

data.collectFirst{ case d: DataD => d.data }

In Rust, I have to pattern match twice to achieve the same result. Is there a way to make it less verbose?

enum MyData {
    DataA(String),
    DataB(u64),
    DataC(bool),
    DataD { data: String, val: u32 },
}

fn main() {
    // test data
    let data = vec![
        MyData::DataB(42),
        MyData::DataD {
            data: "meaning of life".to_owned(),
            val: 42,
        },
        MyData::DataC(false),
    ];

    // find first one that matches and map it
    let found: Option<String> = data
        .iter()
        .find(|d| match **d {
            MyData::DataD { .. } => true,
            _ => false,
        })
        .and_then(|d| match *d {
            MyData::DataD { ref data, .. } => Some(data.to_owned()),
            _ => None,
        });
}
like image 806
kronolynx Avatar asked Mar 05 '18 16:03

kronolynx


1 Answers

Since Rust 1.30, you can use Iterator::find_map:

let found: Option<String> = data.iter().find_map(|d| match d {
    MyData::DataD { data, .. } => Some(data.to_owned()),
    _ => None,
});

Before then, you can use Iterator::filter_map and Iterator::next:

let found: Option<String> = data
    .iter()
    .filter_map(|d| match d {
        MyData::DataD { data, .. } => Some(data.to_owned()),
        _ => None,
    })
    .next();

I don't really like having big match statements in my iterator chains, so I'd normally make a method on MyData to use instead:

enum MyData {
    DataA(String),
    DataB(u64),
    DataC(bool),
    DataD { data: String, val: u32 },
}

impl MyData {
    fn as_data_d_data(&self) -> Option<&str> {
        match self {
            MyData::DataD { data, .. } => Some(data),
            _ => None,
        }
    }
}

fn main() {
    let data = vec![
        MyData::DataB(42),
        MyData::DataD {
            data: "meaning of life".to_owned(),
            val: 42,
        },
        MyData::DataC(false),
    ];

    let found: Option<String> = data
        .iter()
        .find_map(MyData::as_data_d_data)
        .map(str::to_owned);
}

In fact, a lot of my enums have this kind of pattern:

enum MyData {
    DataA(String),
    DataB(u64),
    DataC(bool),
    DataD(D), // made into a struct
}

impl MyData {
    fn is_a(&self) -> bool {
        match *self {
            MyData::DataA(..) => true,
            _ => false,
        }
    }

    fn as_a(&self) -> Option<&String> {
        match self {
            MyData::DataA(x) => Some(x),
            _ => None,
        }
    }

    fn into_a(self) -> Option<String> {
        match self {
            MyData::DataA(x) => Some(x),
            _ => None,
        }
    }

    // Repeat for all variants
}

I've even created a crate to automatically derive these functions. I've never published it because I'm pretty sure something similar already exists and I just haven't found it...

like image 81
Shepmaster Avatar answered Oct 10 '22 15:10

Shepmaster