I am trying to make the return type of a function more
general. Currently, I've had to give it a very specific type which
means I am making some unnecessary type conversions.
While playing around I think maybe the solution is to use Into; I
could have the function return an Into type which would allow me to
convert it but only if necessary.
In the first function, we have a concrete type. In the second, we use
impl and this works and compiles. In the last case, I return an Into
type. This fails, though because the return statement returns a String. But String implements Into<String> I though, because all
things implement Into<Themselves>.
Generic argument is decided by the caller, not the implementor. You can treat it as a function argument but in the type level. If the third function compiles, what type can the expression below have?
h::<&'static str>()
It manually set the type S as &'static str using the turbofish syntax. Now the S is the &'static str which satisfy the Into<String> generic bound. And the S is the return type of the function. Does its body returns &'static str now?
In other words: to_string() returns a String, not any type S that implements Into<String>. This means that you could convert an instance of S to a String, if you had one. It does NOT mean you can convert a String into an instance of S.
This is more formally expressed by the tools of first-order logic, as follows.
impl Trait in return position is called an "existential type". The signature fn g() -> impl Into<String> means: "There exists some concrete type that I choose to return. I don't tell you what exactly it is, just that it is guaranteed to implement Into<String>". A return-position impl Trait thus gives guarantees to the caller.
On the other hand, generic <Arguments> are called "universally-quantified". A declaration like fn h<S: Into<String>>() -> S means: "You are allowed to choose any type S as long as it implements Into<String>. Therefore, a generic type parameter gives guarantees to the callee.
This particular one is a rather odd bound for a return type, though, because it doesn't say anything about how such a type can be instantiated, therefore the only correct implementation of such a function is a trivial, diverging one, e.g. panic!() or an infinite loop {}.
What you could reasonably write is this:
fn h<S: From<String>>() -> S {
S::from("hello".to_string())
}
where the S: From<String> trait bound expresses the fact that you can create an instance of S from a String. It still lets the caller choose the type, though.
Note that impl Traitin argument position is more or less equivalent with a generic type parameter, therefore it is universal in that context, and not existential.
Thank you for the detailed explanation. So the best that I have come up with to achieve something similar, is to use an enum and then implement Info over that.
In this case, the enum has three types and I only have to convert from something else to a String in one case. But if I wanted to get the number out I could do so.
Does this seem a reasonable way to address the problem?