Inline typed into

I'm currently not on my work station so I'm going of memory, please correct me if my assumptions are wrong or if the compiler has changed already.

Basically I had a scenario a while back where I had to do an inline into call. Unfortunately into requires to know what type you want to turn into, however the compiler couldn’t pickup the type on its own. I tried using generics but it seems like into doesn’t except any generics. Obviously the final solution is to wrap the into in a block like this:

{
   let temp:Type = my_var.into();
   temp
}

I would propose a syntax like as but for the into trait:

my_var to Type

or

my_var into Type

But adding a language feature like that surely has some bad points. Although I'm curious why: my_var.into::<Type>() isn’t a feature yet as to me it seems natural.

edit: just remembered that the From trait exist, however you can’t implement it for foreign types right?

You can actually implement From for upstream types in one specific case: when the type being converted from is local, e.g. impl From<LocalType> for ForeignType. There essentially isn't any case where you can implement Into<B> for A but can't implement From<A> for B anymore, and you should always prefer implementing From.

An alternative way to specify an into type is Into::<Type>::into(my_var), as annoying as it is. Various crates provide a helper blanket crate that provide some form of a turbofish-available .into_t::<Type>(), with varying amounts of usage; most people generally see such a utility as not really worth the dependency. One such option is tap::Conv.

2 Likes

The correct solution to your issue is Type::from(my_var). If a foreign library has implemented impl Into<Type> for Foo instead of impl From<Foo> for Type, then it's a bug in the library, since the latter impl is stricly more flexible and should always be preferred (conversely, trait bounds in generics should prefer Into). Which happens, unfortunately, and it sucks when it does, but it's not a language-level issue.

1 Like

You used to not be able to implement From for foreign types, but the orphan rules were relaxed to allow implementing generic traits where the first type parameter is a local type.

IIRC there is a crate which provides a version of Into which has the generic on the method instead of the trait, but I don’t recall the name.

It is possible to fill out the generic with Into as well though: <_ as Into<Type>>::into(value).

2 Likes

Ah, I get where you're coming from. Rust's type inference is generally good, but in cases like these, it does trip up. The into method doesn't play well with generics directly for type inference reasons, as you've noticed. Your solution with the block is one common way to deal with it, although it does make the code a bit more verbose.

The my_var.into::() syntax sounds like it could be useful, but I suspect it might introduce complexity or break some existing rules around trait methods. Plus, Rust tends to be conservative with language changes, so they would probably need a compelling reason to add this.

And yeah, you're right about the From trait—you can't implement it for foreign types due to the orphan rule, so it's not always a direct replacement for Into.

Interesting thought, though. Would love to see if this generates any discussion in the Rust community.

So, there's a bit of history here. In old versions of Rust, it wasn't possible to write impl From<MyType> for StdType, because that's an implementation of an upstream trait for an upstream type, and you had to use impl Into<StdType> for MyType instead. Nowadays, From<MyType> is considered as a local trait for impl coherence if MyType is local, so that impl is valid.

If it were never necessary to write impl Into from the beginning, the trait might have been designed differently; namely, as

trait Into: Sized {
    fn into<T: From<Self>>(self) -> T { T::from(self) }
}
impl<T> Into for T {}

Note how this is different from the trait as it's actually defined:

trait Into<T>: Sized {
    fn into(self) -> T;
}
impl<T, U: From<T>> Into<U> for T {
    fn into(self) -> U { U::from(self) }
}

var.into::<U>() applies the turbofish type to the function, so it would ascribe the output type with the function generic spelling, but is a compile error for the trait generic spelling, since the function isn't generic. Making var.into::<U>() work as-is to specify the trait generic would indeed break uses of generic methods on generic traits, since which generic is being specified would then be ambiguous.

Technically this could theoretically be "fixed" by making a new trait Into and putting that one into the editionNext prelude instead of the current one — it's already the case that ? on Result only works with From impls, not Into — but this is an extremely inelegant hack to fix a fairly minor papercut. That definition is basically what tap::Conv uses, it just suffers from not being default available like Into.

IMHO, the best "solution" is something that's also desirable for other reasons — universal method call syntax (UMCS), i.e. permitting you to write something like var.(U::from)() instead of needing the Into trait to forward method syntax to From::from. It is unfortunate that the first exposure to turbofish syntax is usually collect's generic return type, and into doesn't work the same way, but this does offer a chance to explain what a turbofish is actually specifying, and how Into::into differs from Iterator::collect.

4 Likes

Or if this is going to be used a lot, adding a simple trait can provide the nicer syntax.

trait To {
    fn to<Dst>(self) -> Dst
    where
        Self: Into<Dst>,
    {
        self.into()
    }
}
impl<Src> To for Src {}

Then you can use var.to::<U>().

2 Likes

It would definitely be handy to add a custom error message explaining why turbofish syntax on the into trait function doesn’t. But, could their not be exception and patch the into trait function as a builtin, where when not using the turbofish syntax the old into trait is used and otherwise use the U::from(self)?