Ergonomics of Option<&T> and Cow

APIs in the project I'm working on, take a lot of Option<&PartInfo> arguments. In the implementation, I need to call some methods on PartInfo so I need to unwrap it or create a Default PartInfo.
I want it to be as ergonomic as possible, so I'm looking for different approaches to this.

As we know,

// part is Option<&PartInfo>
let part = part.unwrap_or_default();

Won't work due to the different types (&T vs T). One solution could be:

let part = part.cloned().unwrap_or_default()

but this requires T: Clone and it's not cheap.

This won't work too, due to ToOwned bound on Cow:

let part = part.map(Cow::Borrow).unwrap_or_else(||Cow::Owned(PartInfo::default)));

Of course, I can always do:

let tmp;
let part = match part {
    Some(r) => r,
    None => {
        tmp = PartInfo::default();
        &tmp
    }
}

I even have a macro for this.
But I'm not satisfied with this, I feel like Rust should offer something for this case in std. A type the whole purpose of which is to be either a reference or a value and provide a Deref for it. That's it, nothing more.

I went ahead and made a simple one for my lib (Rov - RefenceOrValue)


enum Rov<'a, T: Sized> {
    Ref(&'a T),
    Val(T)
}

impl<'a, T> Deref for Rov<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        match self {
            Row::Ref(r) => r,
            Row::Val(v) => &v
        }
    }
}

I'm wondering, should Rust have a type like this?

I know there are a bunch of Cow-like crates that do exactly this (and more) but again, to me, it feels like it should be in std.
Now, the second part. To solve my problem with unwrapping an Option<&T> I wrote this:

pub(crate) trait UnwrapAsRov<'a, T> {
    fn unwrap_or_default_rov(&self) -> Rov<'a, T>;
}

impl<'a, T> UnwrapAsRov<'a, T> for Option<&'a T> 
    where T: Default
{
    fn unwrap_or_default_rov(&self) -> Rov<'a, T> {
        match self {
            Some(v) => Rov::Ref(*v),
            None => Rov::Val(T::default())
        }
    }
}

Now I can easily do

let part = part.unwrap_or_default_rov();
part.some_method()

I'd like to hear how others approach this and do you think Rust could/should provide a type like Rov and maybe even provide a method on Option to deal with it?

Your unwrap_or_else solution can work, with some minor changes. If PartInfo implements Clone and Default this should work:

let part = part.map(Cow::Borrowed).unwrap_or_else(||Cow::Owned(PartInfo::default()));

Note that, although the Clone trait is required, it is not used.

You could also create a static reference to your default info. If PartInfo can be constructed in a const context you could use OnceCell or lazy_static to work around that

struct PartInfo {
    a: &'static str,
}

static DEFAULT_PART_INFO: &PartInfo = &PartInfo { a: "A" };

fn take_info(info: Option<&PartInfo>) {
    let info = info.unwrap_or(DEFAULT_PART_INFO);
}
2 Likes

If the type is const constructable you could do

impl PartInfo {
    const fn new() -> Self {
        PartInfo
    }
}

static BASE_PART_INFO: &PartInfo = &PartInfo::new();

impl Default for &'_ PartInfo {
    fn default() -> Self {
        BASE_PART_INFO
    }
}

fn foo(part: Option<&PartInfo>) {
    let part = part.unwrap_or_default();
}

(Or use lazy_static or the like even if it's not const constructable.)

(Ha, @semicoleon beat me!)

1 Like

This is what I was about to suggest too :slightly_smiling_face:

Like how &'a [T] is Default, returning an reference to an empty slice.

Yeah, but my type is not Clone, otherwise I'd use a solution with Cow.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.