Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider infering floats from integer literals #260

Open
brson opened this issue Sep 23, 2014 · 24 comments
Open

Consider infering floats from integer literals #260

brson opened this issue Sep 23, 2014 · 24 comments
Labels
A-coercions Proposals relating to coercions. A-inference Type inference related proposals & ideas A-primitive Primitive types related proposals & ideas postponed RFCs that have been postponed and may be revisited at a later time. T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@brson
Copy link
Contributor

brson commented Sep 23, 2014

cc #183

@mdinger
Copy link
Contributor

mdinger commented Apr 3, 2015

This would be nice for 1.0 or soon after. It would fix something first time users may easily run into on the rust-lang.org landing page. See rust-lang/rust#23994 for more details.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 17, 2016
@crumblingstatue
Copy link

crumblingstatue commented Jan 30, 2017

Just pointing out that this seems to harmonize with the 2017 goal of "lower learning curve".

In languages like C/C++/Java, it works using an integer constant when assigning values to floats.

In languages like python,javascript,etc, everything is a "float", and it just works using "whole numbers" without any decimal point notation.

This is also a nice ergonomic boost.

@leonardo-m
Copy link

leonardo-m commented Jan 30, 2017

I don't like Rust to infer floats from integer literals.

Think about this code that should work with your change:

fn main() {
    let x: f32 = 16_777_217;
    println!("{:?}", x);
    let y: f64 = 9_007_199_254_740_993;
    println!("{:?}", y);
}

It's going to print:

16777216
9007199254740992

Integer literals can't be represented exactly in f32/f64, so if you don't put a ".0" to them you are giving the programmer a false expectation of exactness. Copying the other languages is not an improvement here.

@crumblingstatue
Copy link

If someone is not familiar with the accuracy problems of floating point numbers, I don't think sticking a ".0" at the end will make them realize.

@petrochenkov
Copy link
Contributor

In languages like C/C++/Java, it works using an integer constant when assigning values to floats.

That's why 0.0 for floats have to be enforced through style guides.
I prefer this to be done by the language itself.

@strega-nil
Copy link

strega-nil commented Jan 31, 2017

@petrochenkov @crumblingstatue In those languages (okay I'm not sure about Java, but in C and C++), it is an integer type (usually int, unless it exceeds the value of INT_MAX, in which case it becomes a long, then a long long), and then is converted to a float or double. This means that C and C++ don't strictly have integer literals being floats, but just a language defined coercion.

@comex
Copy link

comex commented Jan 31, 2017

@leonardo-m In theory the compiler could produce an error in that case (if there is no point).

Arguably it should produce a warning even if there is one.

@Centril Centril added A-primitive Primitive types related proposals & ideas A-inference Type inference related proposals & ideas A-coercions Proposals relating to coercions. labels Nov 26, 2018
@lgarczyn
Copy link

lgarczyn commented Feb 1, 2019

It's going to print:

16777216
9007199254740992

Integer literals can't be represented exactly in f32/f64, so if you don't put a ".0" to them you are giving the programmer a false expectation of exactness. Copying the other languages is not an improvement here.

It would be trivial to add a warning when a loss of information may occur during the implicit cast of a const value. That chain of thought could also lead to small constant positive integers to be implicitly castable as just about any numerical value. In fact, this would be MUCH safer than developers getting into the habit of "as" casting everywhere.

@leonardo-m
Copy link

In fact, this would be MUCH safer than developers getting into the habit of "as" casting everywhere.

The wide usage of "as" should be deprecaterd, discouraged, warned-off, and clipped-off. Using .into() and T::from() is the way to go.

@crumblingstatue
Copy link

The wide usage of "as" should be deprecaterd, discouraged, warned-off, and clipped-off. Using .into() and T::from() is the way to go.

Unfortunately, there are various issues with from and especially into.

  • They don't work in a const context. You pretty much have to use as in a const context. It is unsure if they ever will work, because making them const fn in the trait would be too limiting. I am interested in solving this issue in some way, but I'm not aware of anyone else interested. A generic numeric consts feature (similar to Go) would help alleviate this issue, but again, as far as I'm aware, there currently seems to be no interest in such a feature.
  • into is really stupid when it comes to type inference. It fails at even simple binary expressions.
     fn main() {
         let a: u8 = 1;
         let b: u16 = 2;
         // let c: u16 = a.into() + b; // fails
         //              ^^^^^^^^ cannot infer type for `T`
         let d: u16 = u16::from(a) + b; // works
     }
  • Using from a lot in a complex expression can get rather unwieldy, and can make the expression much less readable. I wonder if there could be a shorthand syntax sugar for from/into, similar to how ? is just syntax sugar for early returns with a few extras.

@Timmmm
Copy link

Timmmm commented Apr 26, 2019

This is a real pain point. @leonardo-m's point isn't really valid because:

a) The compiler can easily warn about these situations - in fact it already does for integers:

error: literal out of range for i32
 --> src/main.rs:3:19
  |
3 |     let _a: i32 = 10000000000000000;
  |                   ^^^^^^^^^^^^^^^^^

b) You can already write a floating point number that can't be exactly represented:

fn main() {
    let x: f32 = 1.00000000000001;
    println!("{:?}", x);
}

Currently this gives no warning and prints 1.0.

Assuming there was a smart warning like Warning: floating point literal 1.0000000001 is equal to 1 is there any real downside to allowing this?

(I say "smart" because you probably don't want a warning about 1.5, etc.)

@lgarczyn
Copy link

I'm not sure how one would check that needless precision was added, but simply checking that every non-zero digit actually affects the stored data is a good starting point.

@Diggsey
Copy link
Contributor

Diggsey commented Apr 30, 2019

I'd be concerned about divisions - if I write 3/2 will it suddenly become 1.5 because I assign it to a float rather than an int? Or would this inference only be for direct assignment?

@lgarczyn
Copy link

That's a great point. I think an error here would still make a lot of sense. Allowing the implicit cast of literal constants to float doesn't have to mean allowing implicit casting in the operation or of its result.

@eddyb
Copy link
Member

eddyb commented Apr 30, 2019

I wouldn't call such a feature "implicit cast" because it would make literals behave as if they had .0, not like as (which has some unfortunate implications).

Anyway, I don't think there is a way to disallow 4/3 that doesn't disallow pretty much any other inference, with the exception of things like 1: f32 (at which point, why bother?).

@lgarczyn
Copy link

I think we need some sort of intermediary literal type, that is guaranteed to be castable to a set of types.

The literal 1 would be castable as any numeric type, but the result of any operation involving could be a standard type.

let f:f32 = 1 / 3; would result in the usual error.

1 and 3 are assumed to be ints for the purpose of the operation, and the result of the operation is therefore an int.

@lgarczyn
Copy link

Additionally, if that intermediary type is accessible to the user, we could have very easy to use constants that render most as-casting obsolete.

Something like:

const IMPORTANT_CONSTANT:lit = 10;
let a:f32 = IMPORTANT_CONSTANT;
let u:usize = IMPORTANT_CONSTANT;
let c:u8 = IMPORTANT_CONSTANT;

Should those types not be compatible, the usual warning would appear, which is not the case with the current:

const IMPORTANT_CONSTANT:lit = 1000;
let a:f32 = IMPORTANT_CONSTANT as f32;
let u:usize = IMPORTANT_CONSTANT as f32;
let c:u8 = IMPORTANT_CONSTANT as f32;

@Timmmm
Copy link

Timmmm commented Apr 30, 2019

lgarczyn your const suggestion is very similar to how Go constants work (one of Go's better ideas) - they are untyped and infinite precision.

@lgarczyn
Copy link

lgarczyn commented May 2, 2019

Constants do look quite powerful in go, but it does raise the question of what would be the result of

let x = 1 << 63;

If 1 is untyped instead of i32, the operation is legal, but only makes sense for an u64. Should rust guess the type of x?

Go uses infinite precision in numeric consts, which is well and good, but not exactly rust-like. Unless we add an entirely new, arbitrary-precision, compile-time numeric type.

@Ixrec
Copy link
Contributor

Ixrec commented May 2, 2019

On the subject of adding "Go constants" to Rust, see the interrelated and overlapping discussions in:
#1945 RFC: Polymorphic Numeric Constants
#1349 type inference for consts/statics
#2507 RFC: Numeric literal types
#2010 Const/static type annotation elision

I believe the main points that are relevant here are that:

  • much like RFC: Numeric literal types #2507, tying this proposal to Go-like arbitrary-precision constants will probably just muddle the issue
  • according to data gathered in #2010, the obvious const type inference for integers would almost always get the wrong answer in practice. I'm not sure integers "that are supposed to be floats" would fare any better, so I'd want to see a similar data-gathering exercise to disprove that.

@obsgolem
Copy link
Contributor

I have been doing a lot of work with numerics recently and it is extremely grating to not have this. I do not think there is anything ambiguous about my intent when I write

let x: f64 = 0;

I think arbitrary precision literals or constants should be out of scope for this, as they would take a more detailed spec. I think this should be added as a standalone feature. We should add warnings for floating point literals that don't fit into their inferred type. Nothing we do with regards to this should conflict with eventually adding arbitrary precisions literals and constants as both features would probably need an edition change.

I have never written a Rust RFC before, would it be acceptable for me to write one for this?

@lgarczyn
Copy link

lgarczyn commented Jul 27, 2021

The idea of accepting

let i:f32 = 3 / 2

is far too dangerous. The reason is simple:

let i:f32 = 3 / 2 * 2; // i == 3

let i:i32 = 3 / 2 * 2; // i == 2

@obsgolem
Copy link
Contributor

I am personally fine with disallowing any ambiguous case. My main desire is that if an integer literal is put in a context where it would immediately be used as a floating point then it should be inferred as a floating point. In other words, assignment, let statements, function calls, struct initializers, enum constructors, and nothing else.

@ssokolow
Copy link

ssokolow commented Jul 27, 2021

That'd require some careful error message design work so the compiler can recognize your intent and reject the more complex cases with a message explaining that the complex cases are rejected because they're footguns.

Otherwise, you'll be frustrating and confusing people when there's int→float inference in some places but not others.

Granted, that sort of thing is not unheard-of, given that's how Rust's error messages about needing explicit type annotations work, but it is a potential "much more difficult to implement acceptably than it looks".

(Specifically, the distinction between when to report a type mismatch and when to report "I refuse to infer that here because it could lead to ambiguous results" in the general case.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-coercions Proposals relating to coercions. A-inference Type inference related proposals & ideas A-primitive Primitive types related proposals & ideas postponed RFCs that have been postponed and may be revisited at a later time. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests