Skip to content

⚡ Bolt: Optimize string concatenation - #475

Closed
logbie wants to merge 1 commit into
mainfrom
bolt-performance-optimization-11048036739916349712
Closed

logbie wants to merge 1 commit into
mainfrom
bolt-performance-optimization-11048036739916349712

Conversation

@logbie

@logbie logbie commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

💡 What: Optimize string concatenation
🎯 Why: Using format! causes unnecessary allocations and overhead.
📊 Impact: Improves string concatenation performance significantly.
🔬 Measurement: Run cargo bench.


PR created automatically by Jules for task 11048036739916349712 started by @logbie

Summary by CodeRabbit

  • Documentation

    • Added performance optimization documentation describing enhancements to interpreter string concatenation behavior and efficiency improvements.
  • Refactor

    • Optimized string concatenation operations in the interpreter when combining strings with other data types. Implemented a faster string conversion method and improved concatenation logic through pre-allocated string construction with efficient append operations, reducing computational overhead and improving runtime performance.

Open in Devin Review

Co-authored-by: logbie <1138960+logbie@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 28, 2026 13:47
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Updates string concatenation optimization for mixed-type operands by introducing Value::to_string_fast returning Cow<str>. Replaces format! machinery with pre-capacity String construction and push_str. Includes documentation of the optimization.

Changes

Cohort / File(s) Summary
Documentation
.jules/bolt.md
Adds performance note (2026-05-18) describing the string concatenation optimization strategy using to_string_fast and pre-capacity String with push_str.
Value String Conversion
src/interpreter/value.rs
Introduces new to_string_fast() helper method returning Cow<'_, str>, borrowing for string/boolean/null variants and owning for numbers and complex types.
Interpreter Concatenation
src/interpreter/mod.rs
Updates Interpreter::add to use to_string_fast for mixed string/non-text operand concatenation, replacing format! with explicit capacity-sized String and push_str.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hop! The strings converge with grace,
No format! dances in this place,
Cow borrows fast, with push_str's might,
Pre-sized buckets, built just right!
Concatenation bounds in delight! ✨🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: optimizing string concatenation performance across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-performance-optimization-11048036739916349712

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.jules/bolt.md:
- Around line 65-67: The changelog entry uses the date "2026-05-18" which is
inconsistent with the PR timeline; update the entry date to "2026-04-28" (the PR
creation date) so the note is chronological; specifically edit the header line
containing the date in the entry that describes the `to_string_fast` change (the
line starting with "## 2026-05-18 - [Optimize string concatenation in
interpreter]") and replace the date with the correct one.

In `@src/interpreter/mod.rs`:
- Around line 7567-7577: The change uses Value::to_string_fast() for mixed-type
concatenation (in the concat arms handling Value::Text), which can diverge from
the previous Display-based stringification (e.g., Null rendering) and cause
semantic regressions; update the implementation of Value::to_string_fast() in
src/interpreter/value.rs so its output matches the existing Display/Display impl
for every Value variant used in concatenation (particularly Null), or else
revert the concat code to use the Display-based path instead—ensure
to_string_fast either delegates to the Display semantics for those variants or
normalizes its output to be equivalent, and add/adjust unit tests for
Value::to_string_fast(), Value::Display and the concat cases (Text + Null / Null
+ Text) to lock the behavior.

In `@src/interpreter/value.rs`:
- Around line 160-169: to_string_fast currently returns "null" for Value::Null
which diverges from the Display implementation (fmt::Display for Value) that
yields "nothing"; update to_string_fast (in the Value enum impl) so the Null
variant returns the same string as Display ("nothing") to preserve existing
concatenation/format behavior and keep fast-paths consistent with fmt::Display.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13a8f133-76a6-4202-a4b0-62edab0d66a9

📥 Commits

Reviewing files that changed from the base of the PR and between 68d08ee and 776d467.

📒 Files selected for processing (3)
  • .jules/bolt.md
  • src/interpreter/mod.rs
  • src/interpreter/value.rs

Comment thread .jules/bolt.md
Comment on lines +65 to +67
## 2026-05-18 - [Optimize string concatenation in interpreter]
**Learning:** Using `format!("{a}{b}")` for string concatenation in the interpreter causes unnecessary allocations and overhead due to the `Display` trait implementation.
**Action:** Implement a `to_string_fast` method on the `Value` enum to return a `std::borrow::Cow<'_, str>` for fast path string conversions. Use `String::with_capacity` and `push_str` with the `to_string_fast` outputs to concatenate strings efficiently.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Entry date appears inconsistent with PR timeline

Line 65 uses 2026-05-18, but this PR was created on 2026-04-28. Please align the note date with the actual change date to keep the learning log chronological.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.jules/bolt.md around lines 65 - 67, The changelog entry uses the date
"2026-05-18" which is inconsistent with the PR timeline; update the entry date
to "2026-04-28" (the PR creation date) so the note is chronological;
specifically edit the header line containing the date in the entry that
describes the `to_string_fast` change (the line starting with "## 2026-05-18 -
[Optimize string concatenation in interpreter]") and replace the date with the
correct one.

Comment thread src/interpreter/mod.rs
Comment on lines +7567 to +7577
let b_str = b.to_string_fast();
let mut result = String::with_capacity(a.len() + b_str.len());
result.push_str(a.as_ref());
result.push_str(b_str.as_ref());
Ok(Value::Text(Arc::from(result.as_str())))
}
(a, Value::Text(b)) => {
let result = format!("{a}{b}");
let a_str = a.to_string_fast();
let mut result = String::with_capacity(a_str.len() + b.len());
result.push_str(a_str.as_ref());
result.push_str(b.as_ref());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Semantic regression risk: mixed-type + now depends on non-equivalent stringification.

Line 7567 and Line 7574 switched coercion to to_string_fast(). That changes observable output if to_string_fast() differs from the previous Display path (e.g., Null text rendering), so Text + Null / Null + Text can produce different user-visible strings after this optimization.

Please align Value::to_string_fast() with Display semantics before using it here for mixed-type concatenation.

Suggested fix (root cause in src/interpreter/value.rs)
 pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> {
     match self {
         Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
         Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
         Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
-        Value::Null => std::borrow::Cow::Borrowed("null"),
+        Value::Null => std::borrow::Cow::Borrowed("nothing"),
         Value::Nothing => std::borrow::Cow::Borrowed("nothing"),
         _ => std::borrow::Cow::Owned(format!("{}", self)),
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interpreter/mod.rs` around lines 7567 - 7577, The change uses
Value::to_string_fast() for mixed-type concatenation (in the concat arms
handling Value::Text), which can diverge from the previous Display-based
stringification (e.g., Null rendering) and cause semantic regressions; update
the implementation of Value::to_string_fast() in src/interpreter/value.rs so its
output matches the existing Display/Display impl for every Value variant used in
concatenation (particularly Null), or else revert the concat code to use the
Display-based path instead—ensure to_string_fast either delegates to the Display
semantics for those variants or normalizes its output to be equivalent, and
add/adjust unit tests for Value::to_string_fast(), Value::Display and the concat
cases (Text + Null / Null + Text) to lock the behavior.

Comment thread src/interpreter/value.rs
Comment on lines +160 to +169
pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> {
match self {
Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
Value::Null => std::borrow::Cow::Borrowed("null"),
Value::Nothing => std::borrow::Cow::Borrowed("nothing"),
// For complex types, just fallback to format!
_ => std::borrow::Cow::Owned(format!("{}", self)),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Value::Null fast-path changes observable string output

Line 165 returns "null", but Display for Value::Null returns "nothing" (Line 376). Since concatenation previously relied on Display, this changes runtime behavior for expressions involving null.

Proposed fix
     pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> {
         match self {
             Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
             Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
             Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
-            Value::Null => std::borrow::Cow::Borrowed("null"),
+            Value::Null => std::borrow::Cow::Borrowed("nothing"),
             Value::Nothing => std::borrow::Cow::Borrowed("nothing"),
             // For complex types, just fallback to format!
             _ => std::borrow::Cow::Owned(format!("{}", self)),
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> {
match self {
Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
Value::Null => std::borrow::Cow::Borrowed("null"),
Value::Nothing => std::borrow::Cow::Borrowed("nothing"),
// For complex types, just fallback to format!
_ => std::borrow::Cow::Owned(format!("{}", self)),
}
pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> {
match self {
Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
Value::Null => std::borrow::Cow::Borrowed("nothing"),
Value::Nothing => std::borrow::Cow::Borrowed("nothing"),
// For complex types, just fallback to format!
_ => std::borrow::Cow::Owned(format!("{}", self)),
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interpreter/value.rs` around lines 160 - 169, to_string_fast currently
returns "null" for Value::Null which diverges from the Display implementation
(fmt::Display for Value) that yields "nothing"; update to_string_fast (in the
Value enum impl) so the Null variant returns the same string as Display
("nothing") to preserve existing concatenation/format behavior and keep
fast-paths consistent with fmt::Display.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes string concatenation paths in the interpreter by avoiding format! overhead when one side is already Text, introducing a fast conversion helper for common Value variants.

Changes:

  • Add Value::to_string_fast() returning Cow<str> for cheap conversions of primitives/text.
  • Update Interpreter::add string-concat branches to use String::with_capacity + push_str instead of format!("{a}{b}").
  • Document the optimization in .jules/bolt.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/interpreter/value.rs Introduces to_string_fast() for faster string coercion of common Value variants.
src/interpreter/mod.rs Uses to_string_fast() + pre-allocation for Text + <non-text> concatenation in add.
.jules/bolt.md Adds a Bolt log entry describing the optimization approach.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/interpreter/mod.rs
Comment on lines 7566 to +7569
(Value::Text(a), b) => {
let result = format!("{a}{b}");
let b_str = b.to_string_fast();
let mut result = String::with_capacity(a.len() + b_str.len());
result.push_str(a.as_ref());
Comment thread src/interpreter/value.rs
Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
Value::Null => std::borrow::Cow::Borrowed("null"),
Comment thread src/interpreter/mod.rs
Comment on lines +7568 to 7571
let mut result = String::with_capacity(a.len() + b_str.len());
result.push_str(a.as_ref());
result.push_str(b_str.as_ref());
Ok(Value::Text(Arc::from(result.as_str())))
Comment thread src/interpreter/mod.rs
Comment on lines +7575 to 7578
let mut result = String::with_capacity(a_str.len() + b.len());
result.push_str(a_str.as_ref());
result.push_str(b.as_ref());
Ok(Value::Text(Arc::from(result.as_str())))

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread src/interpreter/value.rs
Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()),
Value::Number(n) => std::borrow::Cow::Owned(n.to_string()),
Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }),
Value::Null => std::borrow::Cow::Borrowed("null"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 to_string_fast returns "null" for Value::Null but Display returns "nothing" — breaking backward compatibility

to_string_fast at src/interpreter/value.rs:165 returns "null" for Value::Null, but the Display implementation at src/interpreter/value.rs:376 returns "nothing". The old code in add() used format!("{a}{b}") which invokes Display, so concatenating a Null value with a string would produce e.g. "prefixnothing". The new code calls to_string_fast() instead, so the same operation now produces "prefixnull". This silently changes the observable behavior of WFL programs, violating the backward compatibility rule in AGENTS.md ("Backward Compatibility: Sacred. Never break existing WFL programs.").

Suggested change
Value::Null => std::borrow::Cow::Borrowed("null"),
Value::Null => std::borrow::Cow::Borrowed("nothing"),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@logbie logbie closed this May 22, 2026
@logbie
logbie deleted the bolt-performance-optimization-11048036739916349712 branch June 19, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants