Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/frequently-requested-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,30 @@ Cross-referencing to other discussions:
* https://github.com/rust-lang/rfcs/issues/1397
* https://github.com/rust-lang/rust/issues/17027
* https://github.com/rust-lang/unsafe-code-guidelines/issues/176

## Implicit Widening

Often there are requests to no longer need to manually perform lossless numeric conversions,
such as from `f32` to `f64`, from `u8` to `i32`, etc.

While that's convenient in trivial cases, it's not necessarily good in more complex cases.
If code was perfect then yes it'd be convenient, but Rust would rather ask which thing you
want between different options when the types don't match.

Take something like this:
Copy link
Member

@tmandry tmandry Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strongest version of this argument to me goes like this. A user might not realize that the following code has a risk of overflow:

// Defined elsewhere, with a less obvious name
struct HasSmallOffset {
    offset: i8,
}

let sm1: HasSmallOffset;
let sm2: HasSmallOffset;
unsafe {
    some_ptr
        .offset(sm1.offset + sm2.offset)
        .write(value);
}

In this case it is clear that the argument type to offset() is unlikely to wrap for normal offset values, but for the offset field this is not so. Requiring a cast forces the user to consider this fact and put the cast in the right place.

The issue though is that detecting this problematic case happens downstream of the actual problematic operation, at the coercion site instead of at the add, and as a result it is very much an overapproximation. This leads to cases where the need to cast is purely a nuisance, like foo[sm1.offset as usize].

I think the places where the current behavior is useful would be better served by a custom integer type that requires more explicit syntax for operations that risk overflow. There is always an opportunity for the user to consider whether they ought to use an integer type like this. Usually multiple, e.g. in this case when writing the HasSmallOffset struct and when performing a narrowing cast from an isize to i8.

A family of types could be provided by the standard library that don't implement traits like Add<Self> but require explicit wrapping methods. Separately, we should consider a feature that allows assigning integer literals to custom integer types to make them more convenient.

```rust
let x: u16 = …;
let y: u16 = …;
takes_u32(x + y);
```

It would certainly be *possible* to have that just compile, as though you'd written
```rust
takes_u32((x+y).into())
```

But having an error there is a good opportunity to ask whether perhaps you wanted
```rust
takes_u32(u32::from(x) + u32::from(y))
```
so that it can't overflow in the addition.
Copy link
Member

@cramertj cramertj Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the desire for users to consider overflow and think about whether upcasting early is the right choice.

However, I don't think it's inherently more problematic to add two u16s that are then used as a u32 (or, more commonly, as a usize) than it is to add two u16s values in the first place.

It's possibly useful to consider what changes we'd like to make if Rust allowed this coercion today. Personally, I don't think this meets the bar for a warn-by-default-lint: false positives would be extremely common. I would expect we'd instead suggest that this should be a clippy lint, one which could be a bit "fuzzier" and capture more information about the context in which the upcasting occurs (for example, is the value a result of an addition or multiplication, or some other operation which could result in an undesirable overflow?).

I think nearly every Rust user has at some point encountered my_vec[x as usize], and in some codebases it's littered everywhere. This is an extremely common papercut that I hear from users coming from other languages (C, C++, Python, Java, Kotlin, TypeScript, etc.) who are surprised at the level of unnecessary strictness and verbosity. It also has a tendency to show up in unsafe code, where the additional verbosity it adds to unsafe indexing makes it harder to verify the correctness of subtle algorithms.

Personally, I think implicit numeric widening + a thoughtful and comprehensive clippy lint is the correct balance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think nearly every Rust user has at some point encountered my_vec[x as usize], and in some codebases it's littered everywhere.

I agree that that's a problem, but it's not obvious to me that widening is the answer.

What if we had something like rustc's IndexVec in alloc? Then if you want to index by u32 everywhere, you'd use BikeshedVec<u32, T> instead of Vec<T>, and you wouldn't need implicit conversions "everywhere" because of course that's the type you're using to index, and indeed if you're ever indexing with usize something weird happened and you get an error about it. That'd let BikeshedVec<u32, T> be smaller too, which would be good for cache usage.

Or even without that, over an edition (because of inference changes) we could just allow indexing with any integer type but still not do widening.

(And widening to usize also adds all kinds of annoying complications around whether u32 can widen to usize and whether we add target-specific type system rules like that.)