Search code examples
rustoption-type

How to convert between Options with Into?


I have in my project a struct A which is logically related to a struct B from a different crate. Both have internally an optional sub-struct (C / D).

Let's say for this example they have this struct definition:

struct D {
    name: Option<String>
}

struct B {
    spec: Option<D>
}

struct C {
    name: Option<String>
}

struct A {
    spec: Option<C>
}

Now I want to implement the Into-trait on A into B:

impl Into<D> for C {
    fn into(self) -> D {
        D {
            name: self.name
        }
    }
}

impl Into<B> for A {
    fn into(self) -> B {
        B {
            spec: self.spec.into()
        }
    }
}

But rust does not allow it:

error[E0277]: the trait bound `std::option::Option<D>: From<std::option::Option<C>>` is not satisfied
   --> src\model\k8s.rs:615:29
    |
615 |             spec: self.spec.into()
    |                             ^^^^ the trait `From<std::option::Option<C>>` is not implemented for `std::option::Option<D>`
    |
    = help: the following implementations were found:
              <std::option::Option<&'a T> as From<&'a std::option::Option<T>>>
              <std::option::Option<&'a mut T> as From<&'a mut std::option::Option<T>>>
              <std::option::Option<&'a tracing_core::span::Id> as From<&'a tracing::span::EnteredSpan>>
              <std::option::Option<&'a tracing_core::span::Id> as From<&'a tracing::span::Span>>
            and 10 others
    = note: required because of the requirements on the impl of `Into<std::option::Option<D>>` for `std::option::Option<C>`

Although I provide a custom implementation for Into on C it only checks for From. Which I can't provide as D is another crate. I have to write this:

spec: if let Some(v) = self.spec { Some(v.into()) } else { None }

Now the question: Is there a better way I am missing? If not, why is it such a hassle to into() Options?


Solution

  • The issue is that you're calling Into::into on the Option<C> type rather than the type the Option holds (C).

    You can use the Option::map method which operates on the inner type of the Option:

    impl Into<B> for A {
        fn into(self) -> B {
            B {
                spec: self.spec.map(Into::into)
            }
        }
    }
    

    There is no blanket impl<T, U: Into<T>> Into<Option<T>> for Option<U> (or the From equivalent) in the standard library, that's why you can't use Into trait to turn Option<T> into Option<U> directly on the Option and have to rely on Option::map or some other way (like your last snippet) of extracting the inner type instead.