Idea: the `into` operator

The Into trait is one of those most frequently used traits in Rust. In many cases it works well with type inference. However in cases it does not work, usually we have to write

let converted = <MyType>::from(v);

In cases that Into was not derived from From, it is more ugly:

let converted = <_ as Into<MyType>>::into(v);

The problem of above is that

  1. It is verbose;
  2. The way to call this and its sementic is like from, but the verb suggest otherwise, a code reader can be confused.

My proposal is to write

let converted = v into Mytype;

and let de-sugaring happen here.

The proposed way the new into keyword should work exactly like as, except that it is pure syntax sugar.

Another way is to introduce a mechanism that allow annotate the into call to specify the type.

let converted = v<as Into<MyType>>.into();

(Note, (v as Into<MyType>).into() technically requires #![feature(unsized_local)], an I am not sure whether it have hidden costs)

2 Likes

Did you know you can write it with type ascription?

let converted: MyType = v.into();
2 Likes

Yes; but this is not possible in a complicated expression.

Here’s a similar issue:

And you might also like this RFC:

2 Likes

If that is a problem, then you can do this

fn into<T, U: Into<T>>(u: U) -> T { u.into() }

which can be called like this into::<i32, _>(foo)

or if you want method syntax

trait IntoGeneral {
    fn into_general<T>(self) -> T where Self: Into<T> { self.into() }
}
impl<T> IntoGeneral for T {}

and can be called like this foo.into_general::<u32>()

name is not important

3 Likes

Given type ascription in expressions, this would just be

let converted = v.into(): Mytype;

So I don’t think I’d be interested in debating into sugar until decisions are made about ascription.

7 Likes

Fair enough.

I think, even type ascription works in this case, it would be better to have a way to distinguish methods from different trait implements. My into keyword does not work either in this case.

This is the piece missing in the current type ascription: (v: impl Clone).clone() is not the same as (v: dyn Into<u8>).into(). Ideally we should be able to do the former because this does not involve unsizing coercion.

I agree: Idea: Paths in method names

Since you can use trait methods directly, it’s as simple as Into::<MyType>::into().

Furthermore, it shouldn’t be the case that Into isn’t defined in terms of From: it’s meant to be syntactic sugar (the method call syntax equivalent) for From, so implementing Into but not From doesn’t really make sense, and implementing Into and From inconsistently with one another would be even worse.

(In case it’s not clear, I’m strongly against any sort of syntactic sugar that just expands to a simple method call on a trait. This could easily be a macro; let’s not pack the language full of even more special-cased traits.)

In the ideal world yes this is what we should do.

In reality however, the orphan rules can prevent us from doing this, making implementing From impossible yet having Into is possible. I learn this from a blog post but I forgot where it was.

It would be better a postfix macro though, which we didn’t have right now.

I like the idea, but it would require a new keyword, so a new edition. Personally I would use the opportunity to fix the as operator. The problem of the as operator is that it may do loosing conversion without warning.

Instead of a new operator, I would modify the as operator to use the conversion trait. If no trait is available, it would fall back to the original behavior for compatibility, but it would raise a warning, Maybe an error in future editions.

1 Like

The as operator is well-defined in the reference, though I do think putting those specifics in the API docs would be helpful. It’s not meant for anything but the simple numeric (truncating / wrapping / padding) and pointer conversions which make sense in a systems programming context and are typically a no-op on most platforms (except for fp conversion).

There are several other issues with making as use conversion traits:

  • Its name is nothing like From or Into, making it difficult to recognize those traits are involved.

  • It gets complicated if you want to support interactions with TryFrom / TryInto: 8i64 as i32? or (8i64 as i32)? (either way, ew), which leads me to:

  • It isn’t backwards compatible with the current integer conversions using as. Integers implement ib: TryFrom<ia> if it can’t guarantee that ((xia as ib) as ia) == xia.