Two questions about `Into`

Hi,

I know I can make passing string values as arguments easier by using Into<String>, which makes it possible to pass both &str and String and will convert &str to String if needed.

fn test<T>(value: T)
    where T: Into<String> {
}

test("foo");
test("bar".to_string());

On the other hand I also know that you can convert a value into an Option, which makes it possible to pass either the value or None:

fn test<T>(value: T)
    where T: Into<Option<String>> {
}

test("foo".to_string());
test(Some("foo".to_string()));
test(None);

But now my questions...

Question 1: How can combine both "features" to convert either a &str or String into Option<String>?

test("foobar");
^^^^ the trait `std::convert::From<&str>` is not implemented for `std::option::Option<std::string::String>`

Question 2 (related): Is it possible to create a custom Into for (or including) primitive types?

impl From<String> for Vec<String> {
    fn from(value: String) -> Self {
        vec![value]
    }
}

Error:

impl From<String> for Vec<String> {
^^^^^------------^^^^^-----------
|    |                |
|    |                `std::vec::Vec` is not defined in the current crate
|    `std::string::String` is not defined in the current crate
impl doesn't use only types from inside the current crate

Any help will be greatly appreciated!

Regards,
Raymond

For your second question, no, you can't implement foreign traits for foreign types. You could create your own intermediate wrapper:

struct VecWrapper(Vec<String>);

impl From<String> for VecWrapper {
    fn from(value: String) -> Self {
        Self(vec![value])
    }
}

impl From<VecWrapper> for Vec<String> {
    fn from(value: VecWrapper) -> Self {
        value.0
    }
}

fn test<T>(value: T)
where
    T: Into<VecWrapper>,
{
    let value: Vec<String> = value.into().into();
}

To answer your first question:

You can do it like this:

fn test<A: Into<Option<B>>, B: Into<String>>(val: A) {
    let val: Option<String> = val.into().map(Into::into);

    println!("{:?}", val);
}

fn main() {
    test("hello");
    test("hallo".to_string());
    test::<_, &str>(Some("hi"));
    test::<_, String>(Some("👋".to_string()));
    test::<_, String>(None);
}

but it's not pretty and you end up having to help the compiler out with the types a bit.

1 Like

@asymmetrikon: Thanks for your example, I played with it and finally even was able to make it possible to convert both &str and String into Vec<String>. It had no real purpose, but I just wanted to understand.

@rrbutani: I tried to understand why you had to specify the types as you did in the last three scenarios, and when I was playing with both your and asymmetrikon's solution, I merged both solutions together and was able to accept &str, String, Some(&str) and Some(String), but I was not able to accept None as it was ambiguous, so I had to specify either <&str> or <String> as type, which is ugly and defeats the purpose.

Thanks for helping me understand this principle!

Regards,
Raymond

You should be able to get it to work more generally: EDIT: Actually this doesn't help with the ambiguity, but it might be a better option anyway, as it doesn't always allocate a String, depending on what you need.

use std::borrow::Borrow;

fn test<A, B>(val: A) 
    where A: Into<Option<B>>, B: Borrow<str>, 
{
    let val: Option<B> = val.into();
    match val {
        Some(b) => println!("{:?}", b.borrow()),
        None => println!("None"),
    }
}

fn main() {
    test("hello");
    test("hallo".to_string());
    test::<_, &str>(Some("hi"));
    test::<_, String>(Some("👋".to_string()));
    test::<_, String>(None);
}

From here, you can always call to_string, on the borrowed result if you really need String: Just convert everything to Option<&str> and then do with that what you wish.