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

Emit warning when using as to directly cast function items to any integral type other than usize #81686

Open
bstrie opened this issue Feb 2, 2021 · 12 comments
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@bstrie
Copy link
Contributor

bstrie commented Feb 2, 2021

Imagine you have a u32. You want to check if it's larger than the maximum value of a u16. Of course, you know that the API will give you a u16, so you will need to cast it to a u32.

You write a unit test. 100 million should be above the range of a u16, you think.

let x = 100_000_000;
let y = u16::max as u32;
println!("{}", x > y);

Output:

false

You begin to doubt your computer science education. In a fugue, you abandon all worldly possessions. You move to the woods. You raise chickens now; sometimes, though, you wonder if they're truly the ones raising you. It is a simpler life.


u16::max is the UFCS'd form of the default'd max method on the prelude'd Ord trait. To a first approximation, there is no reason to ever directly cast a function item to anything other than a usize. The compiler should emit a warning when attempting to cast a function item to u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, and isize. This would not apply to transitive casts; foo as usize as i8 would not warn.

(If I had realized this existed, I would have renamed u16::MAX back when I had the chance.)

@bstrie bstrie added A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Feb 2, 2021
@osa1
Copy link
Contributor

osa1 commented Feb 3, 2021

What would be the "lint group" of this lint? Currently we have: "nonstandard_style", "unused", "rust_2018_idioms", "rustdoc". This one doesn't fit into any of these, I think.

Should this be a clippy lint maybe?

@bstrie bstrie changed the title Emit warning when using as to directly cast function pointers to any integral type other than usize Emit warning when using as to directly cast function items to any integral type other than usize Feb 3, 2021
@bstrie
Copy link
Contributor Author

bstrie commented Feb 3, 2021

@osa1 , allow me to expand on my reasoning as to why I believe this should be in the compiler, rather than clippy.

Ultimately I believe this to be a flaw in the specification of the type cast operator, as. As documented in the reference (https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions), there are four possible categories of type casts that relate to "addresses":

  • "Pointer to address cast": *T where T: Sized as Numeric type [Note: "Numeric type" here is obviously a typo, and is meant to be "Integer type"]
  • "Address to pointer cast": Integer type as *V where V: Sized
  • "Function item to address cast": Function item as Integer type [Note: this specifically is the cast shown in the example above]
  • "Function pointer to address cast": Function pointer as Integer type

It is my opinion that the presence of "Integer type" in all of these "address"-related casts is an error in the language; all instances of "Integer type" should be replaced by "usize".

That said, this would be considered a breaking change if it were to simply be implemented. The only backwards-compatible way of moving forward here is as follows:

  1. Make the use of "address" casts that involve types other than usize into a compiler warning.
  2. In a future edition of Rust (be that 2021 or further in the future), turn the lint from "warn" to "deny" for users who opt-in to the new edition.

This issue is intended to gauge the willingness of the lang team to make such a change. If people are receptive, I would be willing to close this issue in favor of writing a full RFC for the broader change.

The specific example chosen is deliberately horrific; no Rust expert that I have yet shown it to has immediately understood what's happening (excerpted responses from said Rust experts: "OH", "oof", "AH", "oh wow", "oh goddddd"). If the problem were just "<u16 as Ord>::max and u16::MAX are easily confusable", then that is something that would be most appropriate in clippy rather than in the compiler. However, I argue that the underlying problem is more fundamental.

osa1 added a commit to osa1/reference that referenced this issue Feb 4, 2021
Typo originally reported in the compiler issue tracker:

rust-lang/rust#81686 (comment)

It doesn't make sense to cast a pointer to a float, and it's currently
rejected by the compiler:

    error[E0606]: casting `*const T` as `f32` is invalid
     --> test.rs:2:5
      |
    2 |     a as f32
      |     ^^^^^^^^

    error: aborting due to previous error

I think what's meant here is "integer type" rather than "numeric type".
@osa1
Copy link
Contributor

osa1 commented Feb 4, 2021

OK so the long-term goal you have in mind is to restrict pointer to integer casts to usizes. In that case I agree that this check should be in the compiler.

I already implemented the lint. I wasn't sure how to "register" it, but I just realized that we also have built-in lints (like arithmetic_overflow that rejects code like 1i32 << 32) that don't need to be in one of the categories listed in my comment above, so this could be one of those lints. I'll submit a PR later today.

(If anyone could assign this to me that would be helpful)

Regarding disallowing pointer to u32 etc. casts I guess we need to get feedback from the lang team first. Should we mark this as "I-nominated"?

Even if the lang team decides that the change is not desirable perhaps we can still have a warning in the compiler when casting a pointer to a type other than usize. (I'm guessing existing lints like "arithmetic overflow" are also allowed by the spec so maybe that's OK)

@leonardo-m
Copy link

The long-term goal should be to nearly deprecate the usage of naked "as" and use it only for special situations. And use u32::from (here u32::from avoids this problem), try_from, and other future safer conversion functions (like a standard function/macro that performs a range test in debug builds and works like as in release builds).

@bstrie
Copy link
Contributor Author

bstrie commented Feb 5, 2021

I agree that there seems to be a vague and unspoken movement that as is less than ideal and that more principled alternatives should be provided and encouraged. However that's a large, disruptive, long-term goal, and I think the way best to move forward while avoiding total design deadlock is through a series of small and gradual improvements such as this one.

@scottmcm
Copy link
Member

scottmcm commented Feb 7, 2021

This makes good sense to me. I can't imagine any situation in which casting a function to an i8 is what you want.

(I might even be tempted to go further and require going through fn(...), like casting references requires going through *const ..., but as you say that's a bigger conversion and shouldn't block a helpful first step like linting this.)

And to add to the fun, this also happens with

let y = u16::max_value as u32;

which is a whole different kind of typo.

@bstrie
Copy link
Contributor Author

bstrie commented Feb 7, 2021

max_value at least is on track to be deprecated (#68490), which should ameliorate that particular unfortunate quirk.

And yes, I considered a proposal to ban both function item and function pointer to address casts, although your suggestion of only banning the function item cast sounds both less disruptive and totally sufficient for preventing confusion. Though even if that were done I would still propose limiting the remaining address casts to usize.

osa1 added a commit to osa1/rust that referenced this issue Apr 3, 2021
New lint `invalid_ptr_to_int_cast` detects pointer (or function) to
integer casts when the integer is not usize (or u64), and suggests going
through usize, e.g. `x as usize as u32`.

See rust-lang#81686 for motivation.

Closes rust-lang#81686
@bstrie
Copy link
Contributor Author

bstrie commented May 11, 2021

Here's another really bad case:

One of the allowed as casts is called "enum casting": when you have a C-style enum, you can cast a variant to an integer.

enum Foo {
    A,
    B
}

Foo::A as i32; // works

Now, what happens when we add data to A?

enum Foo {
    A(String),
    B
}

Foo::A as i32; // uh oh!

This still compiles! The compiler is supposed to produce a non-primitive cast error (and it would have if we had done Foo::B as i32), but remember that enum variant constructors are just functions, whose address here is cast to an integer.

@scottmcm
Copy link
Member

In addition to a good case for a lint on its own, sounds like another good reason to have an .into_repr() or similar, like the conversation in #81642 (comment).

(Then we could start suggesting that instead of the as, if said trait is implemented, to avoid this possibility.)

@bstrie
Copy link
Contributor Author

bstrie commented Dec 15, 2022

Now that Rust supports explicit discriminants in data-carrying enums in 1.66, this problem is even more acute and easier to stumble into than ever (even the 1.66 release announcement for this feature made it easy for users to stumble into this footgun, see rust-lang/blog.rust-lang.org#1056 ).

@lukas-code
Copy link
Member

Clippy has a lint for this, which we could uplift to rustc: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation

@bstrie
Copy link
Contributor Author

bstrie commented Dec 18, 2023

Note that there's currently an RFC to forbid these casts entirely in the next edition: rust-lang/rfcs#3526

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants