Getter for nested structs with option fields

Hi folks,

I'm after the most idiomatic way to write a getter for a structs like this:

#[derive(Debug)]
struct Third {
    foobar: Option<String>,
}

#[derive(Debug)]
struct Second {
    bar: Option<Third>,
}

#[derive(Debug)]
struct First {
    foo: Option<Second>,
}

fn main() {
    let first = First {
        foo: Some(Second {
            bar: Some(Third {
                foobar: Some("something".to_owned()),
            }),
        }),
    };
}

Writing a getter for the First isn't fun:

impl First {
    fn foobar(&self) -> Option<&str> {
        match &self.foo {
            &None => None,
            &Some(ref foo) => match &foo.bar {
                &None => None,
                &Some(ref bar) => match &bar.foobar {
                    &None => None,
                    &Some(ref foobar) => Some(foobar),
                },
            },
        }
    }
}

I could also use a question mark but then I'd have to write a getter for each struct, not just the parent:

impl Third {
    fn foobar(&self) -> Option<&str> {
        match &self.foobar {
            &None => None,
            &Some(ref foobar) => Some(foobar),
        }
    }
}
impl Second {
    fn bar(&self) -> Option<&Third> {
        match &self.bar {
            &None => None,
            &Some(ref bar) => Some(bar),
        }
    }
}
impl First {
    fn foo(&self) -> Option<&Second> {
        match &self.foo {
            &None => None,
            &Some(ref foo) => Some(foo),
        }
    }
    fn foobar(&self) -> Option<&str> {
        Some(self.foo()?.bar()?.foobar()?)
    }
}

Is there an easier way?

Thank you.

I would write it like this

impl First {
    fn foobar(&self) -> Option<&str> {
        self.foo.as_ref()?.bar.as_ref()?.foobar.as_deref()
    }
}
5 Likes

This is great! Thank you so much @Bruecki

You can also write this as a single match:

impl First {
    fn foobar(&self) -> Option<&str> {
        match self.foo {
            Some(Second {
                bar: Some(Third {
                    foobar: Some(ref foobar),
                }),
            }) => Some(&foobar),
            _ => None,
        }
    }
}

I wouldn’t recommend this over Option::as_ref() specifically, but it might be helpful if you have to access enums that aren't Option.

(If your structs have more fields, use , .. } in the struct pattern to ignore them.)

1 Like