Skip to content

[rust-guard] Rust Guard: Change PathLabelResult.items_path to Option<&'static str> and use Cow for baseline_scope #5594

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Change PathLabelResult.items_path from Option<String> to Option<&'static str>

Category: Type Safety
File(s): guards/github-guard/rust-guard/src/labels/response_paths.rs, guards/github-guard/rust-guard/src/lib.rs
Effort: Small (< 15 min)
Risk: Low

Problem

PathLabelResult.items_path is declared as Option<String> in response_paths.rs (line 27), and PathLabeledOutput.items_path is the same type in lib.rs (line 243). However, every value stored in this field comes from extract_items_array, which returns &'static str. The three assignment sites all call .to_string() on that static string, creating unnecessary heap allocations:

response_paths.rs:213-216  items_path: if items_path.is_empty() { None } else { Some(items_path.to_string()) }
response_paths.rs:311-314  items_path: if items_path.is_empty() { None } else { Some(items_path.to_string()) }
response_paths.rs:628-631  items_path: if items_path.is_empty() { None } else { Some(items_path.to_string()) }

The declared type is lying to the reader — the data is always a compile-time string constant, not a heap-allocated string.

Suggested Change

  1. Change PathLabelResult.items_path in response_paths.rs from Option<String> to Option<&'static str>
  2. Update the three assignment sites to use Some(items_path) directly (no more .to_string())
  3. In lib.rs, change PathLabeledOutput.items_path from Option<String> to Option<&'static str>, and update the assignment at line 815 accordingly (Serde's Serialize handles &'static str without issue)

Before

// response_paths.rs line 27
pub struct PathLabelResult {
    pub labeled_paths: Vec<PathLabelEntry>,
    pub default_labels: Option<crate::ResourceLabels>,
    pub items_path: Option<String>,  // <- heap allocation for a static string
}

// Three identical sites (lines 213-216, 311-314, 628-631):
items_path: if items_path.is_empty() {
    None
} else {
    Some(items_path.to_string())  // <- to_string() on &'static str
},

After

// response_paths.rs line 27
pub struct PathLabelResult {
    pub labeled_paths: Vec<PathLabelEntry>,
    pub default_labels: Option<crate::ResourceLabels>,
    pub items_path: Option<&'static str>,  // <- zero-cost: statically known
}

// Three sites simplify to:
items_path: if items_path.is_empty() {
    None
} else {
    Some(items_path)              // <- borrow, no allocation
},

// lib.rs PathLabeledOutput also changes:
struct PathLabeledOutput {
    labeled_paths: Vec<PathLabel>,
    #[serde(skip_serializing_if = "Option::is_none")]
    default_labels: Option<ResourceLabels>,
    #[serde(skip_serializing_if = "Option::is_none")]
    items_path: Option<&'static str>,  // <- no String allocation before serialization
}

Why This Matters

  • Type accuracy: the type now reflects what the data actually is — a compile-time constant
  • Eliminates 3 heap allocations on every collection labeling call (list_pull_requests, list_issues, list_project_items)
  • Simplifies the 3 assignment sites: the if is_empty() ... else Some(.to_string()) pattern collapses to if is_empty() ... else Some(items_path)
  • In WASM where every allocation has a cost, removing unnecessary String boxing from a hot path is meaningful

Improvement 2: Use Cow<'_, str> for baseline_scope in apply_tool_labels

Category: Performance
File(s): guards/github-guard/rust-guard/src/labels/tool_rules.rs
Effort: Small (< 15 min)
Risk: Low

Problem

apply_tool_labels (line 125) declares baseline_scope as String:

let mut baseline_scope = repo_id.to_string();

The variable is then reassigned 8 times using .to_string() on &'static str constants from scope_names:

line 464:  baseline_scope = scope_names::USER.to_string();
line 485:  baseline_scope = scope_names::GITHUB.to_string();
line 501:  baseline_scope = scope_names::GITHUB.to_string();
line 515:  baseline_scope = scope_names::GITHUB.to_string();
line 587:  baseline_scope = scope_names::GITHUB.to_string();
line 681:  baseline_scope = scope_names::GITHUB.to_string();
line 690:  baseline_scope = scope_names::GITHUB.to_string();
line 717:  baseline_scope = scope_names::USER.to_string();

Each of these allocates a new heap String from a string that already exists as a static constant. Only a few branches genuinely require an owned value (when baseline_scope is set from s_repo_id.clone() or owner.clone()). The variable is only ever consumed at line 728 via &baseline_scope (a deref coercion to &str).

Suggested Change

Change baseline_scope to Cow<'_, str>. Static-constant assignments become Cow::Borrowed; owned-string assignments become Cow::Owned. Add use std::borrow::Cow; to tool_rules.rs imports.

Before

use super::constants::{field_names, scope_names, SENSITIVE_FILE_KEYWORDS, SENSITIVE_FILE_PATTERNS};
// ...

let mut baseline_scope = repo_id.to_string();

// ... later in multiple match arms:
baseline_scope = scope_names::USER.to_string();   // alloc
baseline_scope = scope_names::GITHUB.to_string(); // alloc
// ...
baseline_scope = s_repo_id.clone();               // alloc
baseline_scope = owner.clone();                   // alloc

// Used only as a &str:
ensure_integrity_baseline(&baseline_scope, integrity, ctx)

After

use std::borrow::Cow;
use super::constants::{field_names, scope_names, SENSITIVE_FILE_KEYWORDS, SENSITIVE_FILE_PATTERNS};
// ...

let mut baseline_scope: Cow<'_, str> = Cow::Borrowed(repo_id);

// Static assignments — zero allocation:
baseline_scope = Cow::Borrowed(scope_names::USER);
baseline_scope = Cow::Borrowed(scope_names::GITHUB);

// Owned assignments — allocation only where actually needed:
baseline_scope = Cow::Owned(s_repo_id);    // was s_repo_id.clone()
baseline_scope = Cow::Owned(owner.clone());

// Call site unchanged — Cow<str> derefs to &str:
ensure_integrity_baseline(&baseline_scope, integrity, ctx)

Why This Matters

  • Eliminates 8 heap allocations per apply_tool_labels invocation on common paths (list_gists, dismiss_notification, get_me, get_teams, etc.)
  • scope_names::USER and scope_names::GITHUB are &'static str — there is no reason to ever heap-allocate them
  • Cow::Borrowed is a zero-cost wrapper; the call site &baseline_scope is unchanged (deref coercion works identically)
  • In WASM this matters: allocator calls are the most expensive operation per byte of overhead

Codebase Health Summary

  • Total Rust files: 9
  • Total lines: 13,774
  • Areas analyzed: lib.rs, tools.rs, labels/mod.rs, labels/backend.rs, labels/constants.rs, labels/helpers.rs, labels/response_items.rs, labels/response_paths.rs, labels/tool_rules.rs
  • Areas with no further improvements: tools.rs (BLOCKED_TOOLS const already added; operation predicate tests already suggested)

Generated by Rust Guard Improver • Run: §25791958478

Generated by Rust Guard Improver · ● 1.1M ·

  • expires on May 20, 2026, 10:05 AM UTC

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions