rust-lang / rust-lang/rust-clippy

Ling suggestion: use a range to cycle between values, instead arithmetic remainder.

Open
#7,475 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

What it does

To cycle between 0 and a limit, we usually use an expression like this:

let next_index = (current_index + 1) % ring.len();

The line is easy to understand and works fine. However, the generated code (as shown below) is pretty bad.

An alternative is to use a range:

let next_index = (current_index + 1..ring.len()).next().unwrap_or(0);

The generated code is much smaller and faster, but the source a bit larger and less obvious.

Generated Code

To see why this optimization is important I wrote two functions using both approaches:

pub fn fast_next(current: usize, max: usize) -> usize {
    (current + 1..max).next().unwrap_or(0)
}

pub fn slow_next(current: usize, max: usize) -> usize {
    (current + 1) % max
}

Then:

$ rustc --crate-type lib -g -O nums.rs

$ objdump -d libnums.rlib | c++filt

The code for fast_next:

add    $0x1,%rdi
xor    %eax,%eax
cmp    %rsi,%rdi
cmovb  %rdi,%rax
retq

The code for slow_next:

test   %rsi,%rsi
je     2a <nums::slow_next+0x2a>
add    $0x1,%rdi
mov    %rdi,%rax
or     %rsi,%rax
shr    $0x20,%rax
je     21 <nums::slow_next+0x21>
mov    %rdi,%rax
xor    %edx,%edx
div    %rsi
mov    %rdx,%rax
retq
mov    %edi,%eax
xor    %edx,%edx
div    %esi
mov    %edx,%eax
retq
push   %rax
lea    0x0(%rip),%rdi        # 32 <nums::slow_next+0x32>
lea    0x0(%rip),%rdx        # 39 <nums::slow_next+0x39>
mov    $0x39,%esi
callq  *0x0(%rip)        # 44 <nums::slow_next+0x44>
ud2

Also in https://rust.godbolt.org/z/jb1EPW4Gn.

Categories
  • Kind: I think Pedantic, but maybe Perf.
What is the advantage of the recommended code over the original code
  • Faster and smaller machine code.
Drawbacks

This alternative is invalid in some cases:

  • The divisor is zero or negative.
  • The value is incremented with a step different to 1 (like (a + 2) % b).

The generated code is worse is the divisor is constant and a power of 2 (like (a + 1) % 16).

Example
(a + 1) % b

Could be written as:

(a + 1..b).next().unwrap_or(0)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No implementation files or tests are named. Start by reviewing how rust-clippy defines and tests lints, then evaluate the proposed transformation against the listed invalid cases and confirm that the generated-code benefit justifies a new lint.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.