Skip to main content

wowlab_tidy/languages/rust/rules/performance/
clone_in_loop.rs

1#[cfg(test)]
2use googletest::prelude::*;
3use ra_ap_syntax::{ast, ast::HasArgList};
4
5use super::support::is_inside_loop_body;
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10    Example {
11        label: "clone in for loop",
12        code: "fn f(v: Vec<String>) { for x in &v { let y = x.clone(); } }",
13        pass: false,
14    },
15    Example {
16        label: "clone in while loop",
17        code: "fn f(s: &String) { while true { let y = s.clone(); } }",
18        pass: false,
19    },
20    Example {
21        label: "clone in loop loop",
22        code: "fn f(s: &String) { loop { let y = s.clone(); break; } }",
23        pass: false,
24    },
25    Example {
26        label: "clone outside loop",
27        code: "fn f(s: &String) { let y = s.clone(); }",
28        pass: true,
29    },
30    Example {
31        label: "clone in test module",
32        code: "#[cfg(test)]\nmod tests {\n    fn f(v: Vec<String>) { for x in &v { let y = x.clone(); } }\n}",
33        pass: true,
34    },
35];
36
37crate::ast_rule!(
38    clone_in_loop,
39    "Flag `.clone()` calls inside loop bodies (potential O(n) allocations).",
40    "Cloning inside a loop allocates on every iteration. Borrow or restructure to avoid O(n) heap allocations. \
41     Note: this rule has no type information, so it flags all .clone() calls including cheap ones (Arc, Rc, Copy types). \
42     Suppress with `// #t(rust_clone_in_loop) Arc clone is O(1)` when the clone is intentionally cheap.",
43    Medium,
44);
45
46fn check_clone_in_loop(ctx: &AstCtx<'_>) -> Vec<Violation> {
47    let clone_calls = ctx
48        .nodes::<ast::MethodCallExpr>()
49        .filter(|call| !ctx.is_in_test(call) && is_inside_loop_body(call))
50        .filter(|call| {
51            call.name_ref().is_some_and(|name| name.text() == "clone")
52                && call
53                    .arg_list()
54                    .is_none_or(|arguments| arguments.args().next().is_none())
55        });
56
57    clone_calls
58        .map(|call| {
59            ctx.violation(
60                &call,
61                ".clone() inside a loop — consider borrowing or restructuring to avoid repeated clones",
62            )
63        })
64        .collect()
65}
66
67crate::tidy_ast_test!(check_clone_in_loop, {
68    crate::example_tests!(EXAMPLES, check_clone_in_loop);
69
70    #[gtest]
71    fn clone_in_for_iterator_is_evaluated_once() -> Result<()> {
72        verify_true!(run("fn f(tokens: Tokens) { for token in tokens.clone() {} }").is_empty())?;
73
74        Ok(())
75    }
76});