-
Notifications
You must be signed in to change notification settings - Fork 20
[rust-guard] Rust Guard: Replace heap-allocating to_lowercase() with eq_ignore_ascii_case in is_trusted_first_party_bot #3314
Description
🦀 Rust Guard Improvement Report
Improvement 1: Avoid Heap Allocation in is_trusted_first_party_bot
Category: Performance (WASM-specific) / Type Safety & Idioms
File(s): guards/github-guard/rust-guard/src/labels/helpers.rs
Effort: Small (< 15 min)
Risk: Low
Problem
is_trusted_first_party_bot (line 1294) calls username.to_lowercase() before performing its comparisons. This allocates a new String on the heap on every single call — even when the input is already lowercase ASCII. The function is called on every PR, issue, and commit integrity decision.
This is inconsistent with the established pattern elsewhere in the same file: username_in_list (line 248) uses eq_ignore_ascii_case, which is a zero-allocation in-place comparison. All bot names in the hardcoded list are pure ASCII lowercase, so eq_ignore_ascii_case is semantically identical but allocation-free.
Before
// labels/helpers.rs:1294-1305
pub fn is_trusted_first_party_bot(username: &str) -> bool {
let lower = username.to_lowercase(); // heap allocation every call
lower == "dependabot[bot]"
|| lower == "github-actions[bot]"
|| lower == "github-actions"
|| lower == "app/github-actions"
|| lower == "github-merge-queue[bot]"
|| lower == "copilot"
|| lower == "copilot-swe-agent[bot]"
|| lower == "copilot-swe-agent"
|| lower == "app/copilot-swe-agent"
}After
pub fn is_trusted_first_party_bot(username: &str) -> bool {
username.eq_ignore_ascii_case("dependabot[bot]")
|| username.eq_ignore_ascii_case("github-actions[bot]")
|| username.eq_ignore_ascii_case("github-actions")
|| username.eq_ignore_ascii_case("app/github-actions")
|| username.eq_ignore_ascii_case("github-merge-queue[bot]")
|| username.eq_ignore_ascii_case("copilot")
|| username.eq_ignore_ascii_case("copilot-swe-agent[bot]")
|| username.eq_ignore_ascii_case("copilot-swe-agent")
|| username.eq_ignore_ascii_case("app/copilot-swe-agent")
}Why This Matters
- Zero allocation:
eq_ignore_ascii_casecompares byte-by-byte in place — noStringis created or dropped. - WASM budget: In WASM, every heap allocation goes through the linear memory allocator. Avoiding it on a hot path matters.
- Consistency: The existing helper
username_in_listalready useseq_ignore_ascii_casefor case-insensitive matching; this bringsis_trusted_first_party_botin line with that pattern. - Correctness preserved: All bot name literals are pure ASCII lowercase, so
eq_ignore_ascii_caseproduces identical results toto_lowercase()+==.
Improvement 2: Remove Stale #[allow(dead_code)] from Logging API in lib.rs
Category: Dead Code
File(s): guards/github-guard/rust-guard/src/lib.rs
Effort: Small (< 15 min)
Risk: Low
Problem
Six items in lib.rs carry #[allow(dead_code)] even though they are all actively used:
| Item | Line | Why the attribute is stale |
|---|---|---|
invoke_backend |
99 | pub fn — Rust never emits dead_code for public items; additionally, it's referenced as crate::invoke_backend in labels/backend.rs:85,384,388,546 |
LogLevel enum |
137 | All four variants are used by the log_xxx helpers below |
log_debug |
156 | Called via crate::log_debug() in labels/backend.rs and labels/helpers.rs |
log_info |
161 | Called via crate::log_info() in labels/backend.rs, labels/helpers.rs, labels/tool_rules.rs |
log_warn |
166 | Called via crate::log_warn() in labels/backend.rs |
log_error |
171 | Called via crate::log_error() in labels/backend.rs |
Suppressing dead_code on live code is misleading: it implies the items might be dead (or were dead at some point), making reviewers wonder whether they're safe to delete. It also disables the compiler's ability to alert on a future regression where the functions genuinely become unused.
Before
// lib.rs
#[allow(dead_code)]
pub fn invoke_backend(tool_name: &str, args_json: &str, result_buffer: &mut [u8]) -> Result<usize, i32> { ... }
#[repr(u32)]
#[allow(dead_code)]
pub enum LogLevel { Debug = 0, Info = 1, Warn = 2, Error = 3 }
#[allow(dead_code)]
fn log_debug(msg: &str) { log(LogLevel::Debug, msg); }
#[allow(dead_code)]
fn log_info(msg: &str) { log(LogLevel::Info, msg); }
#[allow(dead_code)]
fn log_warn(msg: &str) { log(LogLevel::Warn, msg); }
#[allow(dead_code)]
fn log_error(msg: &str) { log(LogLevel::Error, msg); }After
pub fn invoke_backend(tool_name: &str, args_json: &str, result_buffer: &mut [u8]) -> Result<usize, i32> { ... }
#[repr(u32)]
pub enum LogLevel { Debug = 0, Info = 1, Warn = 2, Error = 3 }
fn log_debug(msg: &str) { log(LogLevel::Debug, msg); }
fn log_info(msg: &str) { log(LogLevel::Info, msg); }
fn log_warn(msg: &str) { log(LogLevel::Warn, msg); }
fn log_error(msg: &str) { log(LogLevel::Error, msg); }Why This Matters
- Clarity: Removing the annotations signals clearly that these items are live and intentional, not leftover scaffolding.
- Compiler safety net restored: If a future refactor stops calling any of these, the compiler will immediately flag it — but only if
#[allow(dead_code)]isn't there to muffle the warning. - Consistency: Items that are genuinely conditionally-dead use
#[cfg_attr(not(test), allow(dead_code))](e.g.,CollaboratorPermission::logininbackend.rs:74). Plain#[allow(dead_code)]should be reserved for that pattern, not unconditionally live code.
Codebase Health Summary
- Total Rust files: 10
- Total lines: 10,934
- Areas analyzed:
lib.rs,tools.rs,labels/mod.rs,labels/helpers.rs,labels/backend.rs,labels/tool_rules.rs,labels/response_items.rs,labels/response_paths.rs,labels/constants.rs - Areas with no further improvements found (this run): none — first analysis run
Generated by Rust Guard Improver • Run: §24074493118
Generated by Rust Guard Improver · ● 840.4K · ◷
- expires on Apr 14, 2026, 9:38 AM UTC