Hi, I really like rust, sometimes writing of something is really long process and one of the idea I had is creating something like "into" operator to make less code (I know rust is safe, but small amount of code can be even safer,...).
for example
instead "my string".into() or String::from(), or f64::from
Rust generally favors explicit over implicit and .into() is already too easy to abuse. I've seen code that over-uses it to the point where it's impossible to figure out wtf is going on without digging deep into the APIs that are being called. I would much prefer people use more specific conversions wherever possible, eg. .to_string() instead of .into(), and for these methods to be added if they don't yet exist. So I really wouldn't like us to make changes which encourage people to (ab)use .into() even more.
Rust has a very high bar for adding new syntax. Using @foo vs foo.into() only saves 6 characters and I can't imagine the devs will consider that enough of an ergonomics improvement to justify adding a new operator.
While smaller code can be safer by being easier to read, more implicit code is often less safe since (a) you're relying on the compiler to correctly infer the intended types and (b) it's harder for the reader to know what the code is doing if the type information is omitted.
If you really want implicit conversions, a common pattern is to have constructor functions that take a generic impl Into<T>. eg.
Agreed 100%. Doing so for Into::into could be hacked by making it a lang item. The problem is generalizing it to be able to provide the generics of the trait when calling a trait method.
or some other syntax, specifying “here's another location for explicitly constraining / specifying the type T”.
For most trait methods, the types will be sufficiently constrained by the argument type or existing parameters already, so I don't think it's necessary to come up with any approach that would automatically work with existing trait methods, because most trait methods would never need the syntax in the first place.
Adding such a =T parameter, which would be optional like other type parameters with a default, wouldn't be a breaking change, so any existing trait methods (including Into::into) could add support for this in minor releases.
That could work. My immediate question is what if you did Into::<T>::into::<U>(foo) where T != U? Presumably a hard error for an ambiguous call? Syntax bikeshedding aside.
I’d say, either a conservative error ruling out the call syntactically already, or it would be a type mismatch. If the latter approach is chosen, something like Into::<(Foo, _)>::into::<(_, Bar)>(baz) would probably also be expected to work and unify accordingly; same for redundantly specifying the type twice. Redundantly specifying types that are already inferrable is common in Rust anyways, e.g. for type signature on variables in many cases. Incorrectly specifying the type of a variable leads in a type mismatch error.
One somewhat comparable thing is how enum variants can get their arguments in two possible places. They seem to be using a syntactical criterion.
let x: Option<()> = Option::None;
let x: Option<()> = Option::None::<>;
let x: Option<()> = Option::<>::None;
let x: Option<()> = Option::<>::None::<>;
let x: Option<()> = Option::<()>::None;
let x: Option<()> = Option::<()>::None::<>;
let x: Option<()> = Option::None::<()>;
let x: Option<()> = Option::<>::None::<()>; // fails
let x: Option<()> = Option::<()>::None::<()>; // fails
I just had written a lengthy response that was somewhat nonsensical, since I forgot that functions do not support defaulted arguments in the first place
I mean, perhaps it was not completely nonsensical, since defaulted parameters are a reasonable future language addition, and we wouldn’t want to introduce any unnecessary extra complications for such a feature. The basic idea is that if the trait parameters become implicitly added defaulted type parameters to trait methods, then adding normal defaulted parameters (if we get such a feature) would be a breaking change, since it mixes up the order. Well… or, if the order is different, then adding defaulted parameters to the trait would be breaking. Problematic either way.
Indeed this feature would add a first case where a function would have default arguments. There are no semantic questions as to how to handle those in this case though. Usually, the problem is that it’s unclear for functions when the defaults should actually be considered. In this case, the default is to leave the parameter “inferred”. (It is always unified with the trait’s parameter that it’s set to be equal to anyways.)
Makes me wonder: Are there existing proposals for adding something like
fn foo<S, T, U = _>() {}
i.e. type parameters whose default value is “leave this inferred”? Same argument as above, there would be no hard semantic questions to answer for such a feature. It would seem useful to me for making API such as Iterator::map nicer to call, since you wouldn’t need to specify the closure type anymore: .map::<Ty, _>(|x| …) (helps inferring the return type of the closure as Ty) would become somewhat neater, writable as .map::<Ty>(|x| …).
It would also serve as a way of converting impl Trait parameters into named parameters without breakage.
Apologies if this was already proposed, but would it make sense instead to have something like this?
trait IntoExt {
fn to<Other>(self) -> Other where Self: Into<Other>;
}
impl<T> IntoExt for T {
fn to<Other>(self) -> Other where Self: Into<Other> {
Into::into(self)
}
}
Generic bounds stay the same (you still require From/Into) but you'd be able to write
let n = 3u8;
let m = n.to::<u32>();
The trait would not be imported by default, but in a new epoch it could be added to the prelude.
Unfortunately the function cannot be called IntoExt::into because it conflicts with the existing Into::into
is IMO slightly more confusing to look at, as it involves a fully-fledged extra type argument and a constraint, whereas the interpretation of what this means is mostly for syntactical reasons/convenience; so even with this insight, =T could be a reasonable feature even if it’s just syntactic sugar.