rust-lang / rust-lang/rust-clippy

`str::replace` or `str::trim*` intended to be used to remove a prefix/suffix that appears once

Open
#14,511 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

This lint detects instances of str::replace and str::trim* when they are used to remove a known single character or a specific prefix/suffix. In such cases, str::strip_prefix or str::strip_suffix would be a more precise and efficient alternative.

Advantage
  • Using str::replace and str::trim* can unintentionally remove more characters than intended, leading to subtle logic bugs.
  • str::strip_prefix and str::strip_suffix are much more efficient than str::replace, and to a lower extent for str::trim*, as they avoid uselessly scanning a bigger section of the string than necessary.
Drawbacks

No response

Example
pub fn example1(val: String) -> String {
    if val.starts_with(char::is_whitespace) {
        val.replace(char::is_whitespace, "")
    } else {
        val
    }
}

pub fn example2(val: String) -> String {
    if val.starts_with(char::is_whitespace) {
        // or `val.trim_start()`
        val.trim().to_owned()
    } else {
        val
    }
}

pub fn example3(val: String) -> String {
    if val.starts_with('\t') {
        // or `val.trim_start()`
        val.trim().to_owned()
    } else {
        val
    }
}

pub fn example4(val: String) -> String {
    if val.starts_with('\t') {
        val.trim().to_owned()
    } else {
        val
    }
}

pub fn example5(val: String) -> String {
    if val.starts_with('=') {
        val.replace('=', "")
    } else {
        val
    }
}

pub fn example6(val: String) -> String {
    if val.starts_with("abcd") {
        val.replace("abcd", "")
    } else {
        val
    }
}

pub fn example7(val: String) -> String {
    match &*val {
        "=option1" | "=option2" | "=option3" => val.replace('=', ""),
        _ => val,
    }
}

Could be written as:

pub fn example1(val: String) -> String {
    if let Some(val) = val.strip_prefix(char::is_whitespace) {
        val.to_owned()
    } else {
        val
    }
}

pub fn example2(val: String) -> String {
    if let Some(val) = val.strip_prefix(char::is_whitespace) {
        val.to_owned()
    } else {
        val
    }
}

pub fn example3(val: String) -> String {
    if val.starts_with('\t') {
        // ERROR: did you intend on removing just one of the many characters that
        // `char::is_whitespace` (which `str::trim` uses) matches?
    } else {
        val
    }
}

pub fn example4(val: String) -> String {
    if let Some(val) = val.strip_prefix('=') {
        val.to_owned()
    } else {
        val
    }
}

pub fn example5(val: String) -> String {
    if let Some(val) = val.strip_prefix('=') {
        val.to_owned()
    } else {
        val
    }
}

pub fn example6(val: String) -> String {
    if let Some(val) = val.strip_prefix("abcd") {
        val.to_owned()
    } else {
        val
    }
}

pub fn example7(val: String) -> String {
    match &*val {
        "=option1" | "=option2" | "=option3" => {
            // Something along the lines of this
            val.strip_prefix('=').unwrap().to_owned()
        }
        _ => val,
    }
}

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

Start by locating the existing lint implementations and tests for str::replace and str::trim*, then compare their behavior with str::strip_prefix and str::strip_suffix. Use the supplied examples to define which cases should be diagnosed and which should produce an ambiguity warning. Done means the intended cases are linted without incorrectly changing valid uses, with coverage for the examples.

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
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.