rust-lang / rust-lang/rust-clippy
`str::replace` or `str::trim*` intended to be used to remove a prefix/suffix that appears once
Nobody has claimed this yet.
- 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::replaceandstr::trim*can unintentionally remove more characters than intended, leading to subtle logic bugs. str::strip_prefixandstr::strip_suffixare much more efficient thanstr::replace, and to a lower extent forstr::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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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