`GetFrom` trait to mirror `TryFrom`

I'm finding I have a repeated need for an Optional equivalent of TryFrom.

In short, I often have input data that is optional, and output that is expected to be optional. The first Rust idiom I reach for is TryFrom, however this works with Results. I can obviously just use thing.try_into().ok() to convert it into an Option, but it means I need to define a smattering of Errors that are literally never used (can use Error = () instead), and provide no real semantic information other than "this thing can return None, so it did". It also seems like a mismatch in intent - the operation isn't fallible or I would use an Error, it is simply optional, which is entirely valid and not an exceptional case that needs to be handled.

I think a GetFrom trait might be useful for Rust, am I missing something?

Edit: something along these lines:

trait GetFrom {
    fn get_from(self) -> Option<Self>;
}

"Get" being used as it mirrors existing usage of get_ for Options.

You don't need to. The idiomatic type to use when a type carries no information is (). So you can just implement TryFrom with Error = ().

Sure, but isn't this a workaround? It seems like a misuse of Result that should be an Option.

e.g. in any other case If I was defining my Results as Result<T, ()> I would be told to use an Option instead.

Bike shedding aside, a default function could be added to the trait (and to TryInto). You could submit it behind a feature and see what the lib team says.

fn get_from(value: T) -> Option<Self> where Self: TryFrom<T, Error = ()> {
    Self::try_from(value).ok()
}

Or just make your own trait(s).

pub trait GetFrom<T>: Sized {
    fn get_from(value: T) -> Option<Self>;
}

impl<T, U: TryFrom<T, Error=()>> GetFrom<T> for U {
    fn get_from(value: T) -> Option<Self> {
        U::try_from(value).ok()
    }
}

pub trait GetInto<T>: Sized { /* ... */ }
3 Likes

Yeah, this is what I had in mind. Is this worth opening an issue/PR in rustlang? I'm unfamiliar with what the correct process is.

I don't have a good feel for whether the teams would find it too niche or not. You'll have to decide if it's worth it to you or not -- if no one cares enough to try and make it happen on std, it won't.

If there's actually no semantic information to provide, and the operation is semantically fallible, there's nothing wrong with returning Result<T, ()>. If it's a public API, returning some ZST impl Error is better, but anyone complaining about implementing TryFrom<Error=()> is being overly prescriptivist.

The reason to prefer Option<T> over Result<T, ()> in general is that Option is easier to work with and that Result has a fallible semantic often not fully appropriate for -> Option functions. When conforming to a trait's interface, using Result is the correct choice by definition.

If it's for a public API type, it probably is better to use a specific #[derive(Copy, Debug)] struct TConvError; even if you don't do an impl Error, so that you can add impls to it in the future if they become desirable. For internal stuff, it absolutely doesn't matter.

1 Like

The process is to open an ACP first at https://github.com/rust-lang/libs-team/issues/new/choose.

Note that the bar for a new trait is fairly high, since it needs to define the abstract behaviour and is implicitly a "go implement this" request to the ecosystem. My instinct here is that the answer will be "you can just use .ok()". But I'm not the decision-maker for such things.

3 Likes