Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 7 additions & 0 deletions runtime/runtime/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ impl RuntimeValue {
}
}

pub fn into_string(self) -> Result<String, RuntimeError> {
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) {
Expand Down
34 changes: 21 additions & 13 deletions stdlib/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ use techscript_runtime::RuntimeContext;
use techscript_runtime::{error::RuntimeError, error::RuntimeErrorKind, value::RuntimeValue};

fn trim(_ctx: &mut RuntimeContext, args: Vec<RuntimeValue>) -> Result<RuntimeValue, RuntimeError> {
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()))
}

fn replace(
_ctx: &mut RuntimeContext,
args: Vec<RuntimeValue>,
) -> Result<RuntimeValue, RuntimeError> {
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<RuntimeValue>) -> Result<RuntimeValue, RuntimeError> {
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<RuntimeValue> = s
.split(&pat)
.map(|p| RuntimeValue::Str(p.to_string()))
Expand All @@ -33,8 +36,10 @@ fn split(_ctx: &mut RuntimeContext, args: Vec<RuntimeValue>) -> Result<RuntimeVa
}

fn join(_ctx: &mut RuntimeContext, args: Vec<RuntimeValue>) -> Result<RuntimeValue, RuntimeError> {
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()?);
Expand All @@ -44,7 +49,7 @@ fn join(_ctx: &mut RuntimeContext, args: Vec<RuntimeValue>) -> Result<RuntimeVal
Err(RuntimeError::new(
RuntimeErrorKind::TypeMismatch {
expected: "List".to_string(),
found: args[0].runtime_type().to_string(),
found: list.runtime_type().to_string(),
},
None,
None,
Expand All @@ -56,24 +61,27 @@ fn to_lower(
_ctx: &mut RuntimeContext,
args: Vec<RuntimeValue>,
) -> Result<RuntimeValue, RuntimeError> {
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()))
}

fn to_upper(
_ctx: &mut RuntimeContext,
args: Vec<RuntimeValue>,
) -> Result<RuntimeValue, RuntimeError> {
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()))
}

fn contains(
_ctx: &mut RuntimeContext,
args: Vec<RuntimeValue>,
) -> Result<RuntimeValue, RuntimeError> {
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)))
}

Expand Down
Loading