rust-clippy icon indicating copy to clipboard operation
rust-clippy copied to clipboard

`single_range_in_vec_init` stronger than expected, when type is known

Open matzemathics opened this issue 2 years ago • 12 comments

Summary

The single_range_in_vec_init warning seems a bit overly cautious, if the type resulting from an invocation of a vec! macro is already known "from the outside", e.g. via a type annotation or when it is used as an argument / return value.

For example

let foo: Vec<Range<usize>> = vec![1..5]; // causes "warning: a `Vec` of `Range` that is only one element"

In this case clippy might infer, that the user indeed wanted a Vec<Range<...>>, because otherwise the program could not even have type checked. The same is true for this example:

fn barify(v: Vec<Range<usize>>) {
  ...
}

let v = vec![3..42]; // causes "warning: a `Vec` of `Range` that is only one element"
barify(v);

One explicit way to remedy this is using explicit lengths:

let foo = vec![1..5; 1]; // OK

although I am slightly worried that tools like cargo-fmt might at some point opt to remove that.

Lint Name

single_range_in_vec_init

Reproducer

I tried this code:

use std::ops::Range;

fn main() {
    let _foo: Vec<Range<usize>> = vec![1..5];
}

I saw this happen:

warning: a `Vec` of `Range` that is only one element
 --> src/main.rs:4:35
  |
4 |     let _foo: Vec<Range<usize>> = vec![1..5];
  |                                   ^^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_range_in_vec_init
  = note: `#[warn(clippy::single_range_in_vec_init)]` on by default
help: if you wanted a `Vec` that contains the entire range, try
  |
4 |     let _foo: Vec<Range<usize>> = (1..5).collect::<std::vec::Vec<usize>>();
  |                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: if you wanted a `Vec` of len 5, try
  |
4 |     let _foo: Vec<Range<usize>> = vec![1; 5];
  |                                        ~~~~

warning: `single_range` (bin "single_range") generated 1 warning

I expected to see this happen: No warnings produced.

Version

No response

Additional Labels

No response

matzemathics avatar Jul 03 '23 13:07 matzemathics

Ignoring it for arguments when it's a local would likely be pretty tricky, though type annotations should definitely be checked

Centri3 avatar Jul 03 '23 13:07 Centri3

Something not currently covered by the PR:

use std::ops::Range;

struct A {
    ranges: Vec<Range<usize>>,
}

struct B(Vec<Range<usize>>);

fn main() {
    A { ranges: vec![0..5] };
    B(vec![0..5]);
}

The correct type of a struct's field is also known "from the outside".

ArthurCose avatar Jul 16 '23 06:07 ArthurCose

Should be simple enough to fix

Centri3 avatar Jul 16 '23 06:07 Centri3

It ignores ExprFields now. This also means something like Vec<&dyn Any> will be skipped as well but I think due to the rarity of both that and passing a Range<usize> to it, it should be fine. (It's also debatable whether that should even be linted.)

Centri3 avatar Jul 16 '23 07:07 Centri3

I'm also running into this in Quinn, in the tests for the RangeSet::replace() method. This definitely seems like a false positive.

djc avatar Aug 25 '23 07:08 djc

This is coming up with a lot of false positives for our project as well. I'm concerned that it is valid to have/want a Vec with a single Range, and that the suggestions to correct it would alter functionality. If anyone goes and applies them blindly, they might cause subtle bugs.

avantgardnerio avatar Aug 29 '23 14:08 avantgardnerio

The help message should probably be altered to make sure this doesn't happen, since right now it acts as though this is almost certainly what they want

Centri3 avatar Aug 29 '23 15:08 Centri3

It also causes quite annoying issues in my project. I completely don't understand the intention behind this lint and why is it enabled by default.

synek317 avatar Nov 24 '23 00:11 synek317

This lint doesn't tell me what to do if I actually WANT an array/vec of ranges, which makes its strength even more bothersome.

bugadani avatar Oct 02 '24 19:10 bugadani

Agreed with everyone here, in our case it's only triggering on locals that are immediately passed to function calls that wouldn't typecheck otherwise. Should this lint be downgraded to a category that isn't warn-by-default?

smalis-msft avatar Nov 15 '24 17:11 smalis-msft

+1 I have a function:

pub async fn read_ranges(
    &self,
    loc: Location,
    ranges: Vec<Range<u64>>,
) { ... }

.. and a function call:

storage
  .read_ranges(
      loc,
      vec![start as u64..end as u64],
  )
  .await

The auto fix would fail compilation:

storage
  .read_ranges(
      loc,
      (start as u64..end as u64).collect::<std::vec::Vec<u64>>(), // Vec<u64>, while surely we need Vec<Range<u64>>; the content has been wrongly changed anyway.
  )
  .await

tisonkun avatar Dec 09 '25 00:12 tisonkun

I don't understand why this lint is enabled by default too. If you use vec![0..10] where your code expecting a Vec<usize> then you'll get a compiler error anyway. Rust is statically typed language, and the compiler is responsible for such mistakes not the linter.

Lints producing such fix hints shouldn't be enabled by default

help: if you wanted a `Vec` that contains the entire range, try
   |
39 -     let rngs: Vec<Range<usize>> = vec![1..10];
39 +     let rngs: Vec<Range<usize>> = (1..10).collect::<std::vec::Vec<usize>>();
   |
help: if you wanted a `Vec` of len 10, try
   |
39 -     let rngs: Vec<Range<usize>> = vec![1..10];
39 +     let rngs: Vec<Range<usize>> = vec![1; 10];
   |

AmmarAbouZor avatar Dec 11 '25 07:12 AmmarAbouZor