Implementing Into<T> on a generic function doesn't work with &T

Hi folks,

I have a strange issue:

mod something {
    #[derive(Clone, Debug, Eq, PartialEq)]
    pub enum TheFirst {
        First,
        Second,
    }
    impl Into<TheSecond> for TheFirst {
        fn into(self) -> TheSecond {
            TheSecond::Third
        }
    }
    impl Into<TheSecond> for &TheFirst {
        fn into(self) -> TheSecond {
            TheSecond::Third
        }
    }

    #[derive(Clone, Debug, Eq, PartialEq)]
    pub enum TheSecond {
        Third,
        Fourth,
    }
}

fn main() {
    let x = something_with_generic(&something::TheFirst::First);
    println!("{:?}", x);
}

fn something_with_generic<T>(name: &T) -> something::TheSecond
where
    T: Into<something::TheSecond>,
{
    // the trait `From<&T>` is not implemented for `TheSecond`, which is required by `&T: Into<_>
    let x = name.into();
    x
}

It works OK with T but not with &T and I'm not sure how to fix it. Could you please help?

Thank you.

fn something_with_generic<'a, T>(name: &'a T) -> something::TheSecond
where
    &'a T: Into<something::TheSecond>,

(playground)

3 Likes

Thank you!

I wonder if I could use From<> without a need of lifetimes?

Usually From isn’t used too much with references. Sure, some types implement such conversions like e.g. String: From<&str>, but the use cases for such implementations are commonly limited to only

  • the convenience of the user being able to write ….into(), or String::from(…) directly
  • APIs that accept an owned (x: T) where String: From<T> being also callable with references like &str

When an API surface expects something that is always a reference, and still also always convertible, it’s common to not use From anymore, but start considering using your own trait; perhaps you only need the (&First) -> Second conversion anyways and don't need the (First) -> Second at all, your trait could just have a &self method.


The &'a T: Into<something::TheSecond> / something::TheSecond: From<&'a T> approach becomes even more weird if you want to call .into() in your function on a &T reference that borrows a value owned by your own function

Technically, From/Into is still usable then, but you’d have to go into HRTBs like for<'b> &'b T: Into<something::TheSecond>.

A custom trait with a &self method has full support for this use case, too, and without any of this lifetime-weirdness.

1 Like

That makes a lot of sense, thank you @steffahn .

When you say a custom trait, do you mean:

pub trait MyInto {
    fn some_other_name_than_into(&self) -> TheSecond;
}

?

Yes, that’s what I mean. It really depends on your use-case of course whether you can find a fitting name for such a trait and its method. Perhaps you’ll find that the sort of types you want to work with end up requiring more capabilities anyway besides this conversion, so the trait could even get more methods.

Another thing I wanted to mention, because your example only involved simple enum that could be Copy. If your types are all small copyable data, then you could also work with T: Copy + Into<TheSecond>, and work with &T by copying the T, then doing the conversion.

Another use-case could be that T is a type that contains TheSecond, but T isn’t cheap to copy or something… but you’re fine with copying/cloning TheSecond. In this case, you could consider implementing T: AsRef<TheSecond> and do your &T -> TheSecond conversion by first projecting &T to &TheSecond via AsRef and then copying/cloning the result.

There’s too many design possibilities :cowboy_hat_face: