Standardize on From/Into for all conversions


#1

All conversions, especially those of TryFrom and Try, should use From/Into. E.g.

pub trait MyTry where Self: Into<Result<<Self as MyTry>::Ok, <Self as MyTry>::Error>>, <Self as MyTry>::Ok: Into<Self> {
    type Ok;
    type Error;
    fn from_error(e: <Self as MyTry>::Error) -> Self;
}

impl<T, E> MyTry for Result<T, E> where E: Into<Self>, T: Into<Self> {
    type Ok = T;
    type Error = E;
    fn from_error(e: E) -> Self {
        Err(e)
    }
}

#[derive(Eq, PartialEq)]
struct Foo;

// this should be impl<T> From<T> for Result<T, E>
impl<E> Into<Result<Foo, E>> for Foo {
    fn into(self) -> Result<Foo, E> {
        Ok(self)
    }
}

fn main() {
    let o: Result<Foo, Foo> = Foo.into();
    let e: Result<Foo, Foo> = MyTry::from_error(Foo);
    assert!(o != e);
}

(note that from_error is, sadly, still necessary. but also note that we don’t need into_result or from_ok, as those can be done with From/Into! this provides a more generic stdlib, so using it is easier! also note that we automatically get a TryFrom - From for Result<T, E> is equivalent to but better than TryFrom for T!)


#2

But TryFrom can fail, and From can’t.

They are fundamentally different operations.

If I have a guaranteed operation. I don’t want to handle errors. and if I have an error case I need to.


#3

From<U> for Result<T, E> can fail.

e.g.

impl From<u16> for Result<u8, !> {
    fn From(v: u16) -> Self {
        if v > 255 { panic!(); }
        Ok(mem::transmute::<[u8;2]>(v)[0])
    }
}

I use From/Into all the time, let me use it more.


#4

Small note: I think the title of this thread sounds a bit misleading; it sounds like you’re saying we should get rid of TryInto and just use Into<T>; but after reading it I think I see you are suggesting that trait methods like Try::from_ok and Try::into_result should not exist and ought to be provided through Into.

Suggestion: change “all conversions” to “all conversions in traits?”


I’m against this for the same reasoning I was against using Into<Option<T>> for an operator in another thread.

Using one trait (Into) for a large multitude of purposes makes the language less powerful. We will run into cases where we are forced to choose between one feature or another for our type because the Into impls we want to write would overlap.


#5

I believe the general-ness of Into actually makes the language more powerful, even if it could restrict what impls you may use. I use the term “could” here, because I believe newtypes solve that problem.

In the general case, these impls would provide great ergonomics/convenience for most operations. In the few edge cases where they don’t, you’d be able to use newtypes and it would no longer be a big deal.

“Make simple things simple and complex things possible” - someone


#6

My gut reaction is that needing to wrap something in a newtype to use some functionality can be unergonomic for consumers of a type—especially if these values may be inside a container. (e.g. changing Vec<T> to Vec<MyNewtype<T>> requires allocation, and changing &[T] to &[MyNewtype<T>] requires unsafe)

Clearly, we disagree on how big of a problem overlap will be in practice. You don’t believe it will happen often, whereas I fear it will happen all over the place. (As I see it, overlap happens quite often enough even when we aren’t overloading the meaning of traits!).

The only way to know for sure is to try implementing it at a large scale.


#7

See Looking for RFC coauthors on "named impls"

I would like the nightly compiler to have “A/B testing” mode. But I don’t think that’s implemented yet.