Rust is very nitpicky about use of integer literals where floating point types are expected.
let x: (f64,f64) = (0,0); // Not allowed
let x: (f64,f64) = (0.0,0.0); // Not as readable
Similarly:
if y > 0 // not allowed for f64 :(
error: mismatched types:
expected f64,
found _
(expected f64,
found integral variable)
I don't see any harm in allowing implicit conversion from integral literal to floating point type if the value fits without loss of precision. I think it would allow avoiding unnecessary noise in the code and save me from frequent compilation errors where I forget a "decorative" .0.
The abundance of .0 also makes code less clear when working with tuples: 1.0 + p.0
BTW: the widening discussion doesn't seem to cover integer > float conversions. Should it?
I don’t think C(++) is a good reference here, because in C implicit type coercion is lax and thus not trusted. What I’m proposing here is very limited, so it doesn’t require C level of paranoia.
I think it’s a bad coding style and hence should not be encouraged by allowing auto casting, especially because of the type inference :
let x = (0, 0);
Did you mean float or int here ? Obviously, in some case you are required to write the plain type, i know type suffixes will solve that, but i’m not sure if allowing casting in some case is a good practice to adopt.
I mean relaxing specific cases where required type is already known, so there is no ambiguity, and the only thing preventing it from working correctly is a deliberate error condition:
I think the answer to that question is a resounding “yes”.
When I personally am using integer or floating point literals, I already have a pretty solid context. I’m not thinking of how many bits it makes up or if it’s signed or not or if it’s integral or floating, I’m thinking of it as a good, old-fashioned decimal. And if this trivial change saves me from having to find everywhere I used 1 instead of 1.0 when their meanings are semantically and numerically equivalent, then I +1 the hell out of it.
Rust has enough common compiler errors for the user to deal with. No one should have to waste their time correcting purely pedantic errors like this one.
"Purely pedantic" errors help writing unambiguous code, like not allowing string + integer although it has very clear semantics (append the integer as decimal number).
But is let float: f32 = 1;at all ambiguous? You’d type that without thinking; I do it all the time. It makes perfect sense to me, and works fine in other languages. Thus why I think this change is necessary and the issue pedantic.
I cannot think of one single instance where using an integer literal to represent a floating-point would create a logic error. The compiler would error on any mismatch or real ambiguity, and there’s no information loss because this change doesn’t add the ability to assign a float literal as an integral value.
@tbu, but that has nothing to do with the feature I’m proposing.
I’m not proposing changes to type inference. I’m not proposing C-like type conversions that cause loss of precision and unexpected behavior. I’m not proposing allowing adding strings to integers!
I’m proposing to remove a very specific error that happens in a very unambiguous case where the required type has already been inferred, so it’s absolutely unambiguous that you have an integer literal used in context where floating point type is expected, and it’s possible to guarantee that the result will be exactly identical as if you added . to the literal.
I think I did understand you and wrote the above comment with that in mind. A constraint to f32 means that the type inference can decide that the literal needs to be a f32. I still wouldn’t call that “absolutely unambiguous”.
See e.g.:
let foo = if bar == baz {
(0..1000).map(|_| 'a').collect()
} else {
[...]
format!("Yay!")
}
I recently stumbled upon such a code fragment, imagine there’s lots of code instead of the [...]. Although it’s entirely obvious to the compiler that the code in the first if branch must return a String, I would initially assume it’s a Vec<char> because I didn’t see any constraints and Vec<T> is what collect defaults to. I can imagine that similarly such a float inference is non-obvious.
Also please note that in situation where there is no explicit type constraint to f32/f64 my proposal simply does not apply, because the error “expected f64, found integral variable” won’t happen.
Let me ask a question, starting with a modified version of @tbu's example:
fn main() {
let bar = true;
let baz = true;
let foo = if bar == baz {
(0..10).map(|_| 1).collect() // line 5
} else {
println!("Else statement!");
let v : Vec<f32> = [...]; // line 8
[...]
v
};
println!("Foo: {:?}", foo);
}
Does this count as "completely unambiguous"? To the compiler, its clearly unambiguous: foo is definitely a Vec<f32>, so on line 5, we must be talking about f32s.
It sounds to me like you're saying that that doesn't count as "completely unambiguous", that the only thing that counts as "completely unambiguous" is to have the type hint on the same line. However, I don't think there are any places in the compiler that treat "on the same line" as any more or less ambiguous than the example above; are you suggesting that we special-case this, just for float literals?