From 59efc4f178727779918ef93e3fcd6236586087cb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 05:05:29 +0000 Subject: [PATCH] Optimize string allocations in stdlib strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Replaced try_into_string() with into_string() where passing owned strings in stdlib/src/strings.rs. 🎯 Why: Avoids unnecessary string cloning when taking ownership. 📊 Impact: Reduces heap allocations and latency when invoking standard library functions. 🔬 Measurement: Observe memory usage during stdlib string operations. Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ runtime/runtime/src/value.rs | 7 +++++++ stdlib/src/strings.rs | 34 +++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 8b1b1d11..f9e12c68 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -78,3 +78,7 @@ ## 2024-06-25 - Incorrect Micro-optimization of `iter().find()` **Learning:** Replaced `iter().find(...)` with a manual `for` loop in `runtime/android_runtime/src/ui_model.rs`. The code review rejected this optimization because in Rust, iterators are zero-cost abstractions and `iter().find()` is highly optimized and often compiles to the exact same or better assembly via internal iteration. Expanding it into a manual `for` loop degrades code readability and conciseness while providing zero performance benefit. **Action:** Never replace simple, single-pass idiomatic iterators like `.iter().find()` with manual loops, as these are zero-cost abstractions and replacing them decreases readability without any performance gain. + +## 2024-05-24 - String allocation optimization in stdlib strings +**Learning:** Extracting string arguments via `try_into_string()` inside `stdlib/src/strings.rs` was unnecessarily cloning `String` objects due to passing owned values. Passing arguments by taking ownership and using `.into_string()` prevents cloning when the underlying argument is already a `RuntimeValue::Str`. +**Action:** Replaced `.try_into_string()` with `.into_string()` after obtaining ownership of the arguments through `args.into_iter()` to remove unnecessary heap allocations and string cloning. diff --git a/runtime/runtime/src/value.rs b/runtime/runtime/src/value.rs index b9f91bdc..5f80fd95 100644 --- a/runtime/runtime/src/value.rs +++ b/runtime/runtime/src/value.rs @@ -212,6 +212,13 @@ impl RuntimeValue { } } + pub fn into_string(self) -> Result { + match self { + Self::Str(s) => Ok(s), + other => other.try_into_string(), + } + } + /// Verifies reference identity check (===). pub fn physical_eq(&self, other: &Self) -> bool { match (self, other) { diff --git a/stdlib/src/strings.rs b/stdlib/src/strings.rs index b24b2f05..dcdfa212 100644 --- a/stdlib/src/strings.rs +++ b/stdlib/src/strings.rs @@ -5,7 +5,8 @@ use techscript_runtime::RuntimeContext; use techscript_runtime::{error::RuntimeError, error::RuntimeErrorKind, value::RuntimeValue}; fn trim(_ctx: &mut RuntimeContext, args: Vec) -> Result { - let s = args[0].try_into_string()?; + let mut args_iter = args.into_iter(); + let s = args_iter.next().unwrap().into_string()?; Ok(RuntimeValue::Str(s.trim().to_string())) } @@ -13,15 +14,17 @@ fn replace( _ctx: &mut RuntimeContext, args: Vec, ) -> Result { - let source = args[0].try_into_string()?; - let from = args[1].try_into_string()?; - let to = args[2].try_into_string()?; + let mut args_iter = args.into_iter(); + let source = args_iter.next().unwrap().into_string()?; + let from = args_iter.next().unwrap().into_string()?; + let to = args_iter.next().unwrap().into_string()?; Ok(RuntimeValue::Str(source.replace(&from, &to))) } fn split(_ctx: &mut RuntimeContext, args: Vec) -> Result { - let s = args[0].try_into_string()?; - let pat = args[1].try_into_string()?; + let mut args_iter = args.into_iter(); + let s = args_iter.next().unwrap().into_string()?; + let pat = args_iter.next().unwrap().into_string()?; let parts: Vec = s .split(&pat) .map(|p| RuntimeValue::Str(p.to_string())) @@ -33,8 +36,10 @@ fn split(_ctx: &mut RuntimeContext, args: Vec) -> Result) -> Result { - let sep = args[1].try_into_string()?; - if let RuntimeValue::List { items, .. } = &args[0] { + let mut args_iter = args.into_iter(); + let list = args_iter.next().unwrap(); + let sep = args_iter.next().unwrap().into_string()?; + if let RuntimeValue::List { items, .. } = &list { let mut str_parts = Vec::new(); for item in items.borrow().iter() { str_parts.push(item.try_into_string()?); @@ -44,7 +49,7 @@ fn join(_ctx: &mut RuntimeContext, args: Vec) -> Result, ) -> Result { - let s = args[0].try_into_string()?; + let mut args_iter = args.into_iter(); + let s = args_iter.next().unwrap().into_string()?; Ok(RuntimeValue::Str(s.to_lowercase())) } @@ -64,7 +70,8 @@ fn to_upper( _ctx: &mut RuntimeContext, args: Vec, ) -> Result { - let s = args[0].try_into_string()?; + let mut args_iter = args.into_iter(); + let s = args_iter.next().unwrap().into_string()?; Ok(RuntimeValue::Str(s.to_uppercase())) } @@ -72,8 +79,9 @@ fn contains( _ctx: &mut RuntimeContext, args: Vec, ) -> Result { - let s = args[0].try_into_string()?; - let sub = args[1].try_into_string()?; + let mut args_iter = args.into_iter(); + let s = args_iter.next().unwrap().into_string()?; + let sub = args_iter.next().unwrap().into_string()?; Ok(RuntimeValue::Bool(s.contains(&sub))) }