Skip to content

feat: add LazyValue::as_cow_str#217

Closed
utkarshgupta137 wants to merge 2 commits intocloudwego:mainfrom
utkarshgupta137:main
Closed

feat: add LazyValue::as_cow_str#217
utkarshgupta137 wants to merge 2 commits intocloudwego:mainfrom
utkarshgupta137:main

Conversation

@utkarshgupta137
Copy link
Copy Markdown
Contributor

What type of PR is this?

feat

Check the PR title.

  • This PR title match the format: <type>(optional scope): <description>
  • The description of this PR title is user-oriented and clear enough for others to understand.
  • Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. User docs repo

More detailed description for this PR.

Add LazyValue::as_cow_str to support zero-copy get.

Currently, sonic-rs::get returns LazyValue, but LazyValue::as_str returns &str which is tied to the lifetime of the LazyValue reference instead of the input. This means that even if there are no escapes, we've to copy the str to return the value. LazyValue::as_cow_str returns a Cow<'a, str> which is tied to the lifetime of the input.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 3, 2026

CLA assistant check
All committers have signed the CLA.

@liuq19
Copy link
Copy Markdown
Collaborator

liuq19 commented Mar 18, 2026

@codex

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 0% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.51%. Comparing base (e339451) to head (983eda2).

Files with missing lines Patch % Lines
src/lazyvalue/value.rs 0.00% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #217      +/-   ##
==========================================
- Coverage   70.60%   70.51%   -0.10%     
==========================================
  Files          42       42              
  Lines        9550     9563      +13     
==========================================
  Hits         6743     6743              
- Misses       2807     2820      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@liuq19
Copy link
Copy Markdown
Collaborator

liuq19 commented Mar 31, 2026

Thanks for the contribution! The motivation behind zero-copy get makes sense.

However, I'm hesitant to add this API to sonic-rs for a couple of reasons:

API surface bloat and naming confusion

LazyValue already exposes three levels of raw accessors (as_raw_str, as_raw_cow, as_raw_faststr) plus the trait method as_str. Adding as_cow_str alongside the existing as_raw_cow creates a confusing pair — the names only differ in word order, but one returns raw JSON (with quotes) and the other returns the parsed string (without quotes). This is easy to mix up and hard to discover correctly.

If we kept going down this path, we'd end up with a 3×2 matrix of methods (raw vs parsed × &str / Cow / FastStr), which is too many for what should be a simple type.

This can be implemented on the user side

Users who need a Cow<'a, str> tied to the input lifetime can achieve the same thing with the existing API:

let lv: LazyValue = sonic_rs::get(&input, &["key"]).unwrap();
let cow: Cow<'_, str> = match lv.as_raw_cow() {
    Cow::Borrowed(s) if !s.contains('\\') => Cow::Borrowed(&s[1..s.len() - 1]),
    _ => Cow::Owned(lv.as_str().unwrap().to_string()),
};

It's a few more lines, but it keeps the library API lean and avoids the naming ambiguity.

I'd prefer to keep LazyValue's API surface small and let users compose what they need from the existing primitives. Thanks again for the PR though — I appreciate the thought you put into it!

@liuq19 liuq19 closed this Mar 31, 2026
@utkarshgupta137
Copy link
Copy Markdown
Contributor Author

Thanks for the contribution! The motivation behind zero-copy get makes sense.

However, I'm hesitant to add this API to sonic-rs for a couple of reasons:

API surface bloat and naming confusion

LazyValue already exposes three levels of raw accessors (as_raw_str, as_raw_cow, as_raw_faststr) plus the trait method as_str. Adding as_cow_str alongside the existing as_raw_cow creates a confusing pair — the names only differ in word order, but one returns raw JSON (with quotes) and the other returns the parsed string (without quotes). This is easy to mix up and hard to discover correctly.

I don't have a preference for name. Maybe it could be as_str_borrowed or as_borrowed_str.

If we kept going down this path, we'd end up with a 3×2 matrix of methods (raw vs parsed × &str / Cow / FastStr), which is too many for what should be a simple type.

I see your reasoning, but I disagree. The JsonValueTrait doesn't have functions for Cow/FastStr, only str. So this is a version of JsonValueTrait::as_str which borrows from the input. I originally tried to add this function to the trait itself (similar to how serde has both visit_str & visit_borrowed_str), but it created more changes than necessary, so I opted for this version. Perhaps you would be more comfortable adding JsonValueTrait::as_borrowed_str?

This can be implemented on the user side

Users who need a Cow<'a, str> tied to the input lifetime can achieve the same thing with the existing API:

let lv: LazyValue = sonic_rs::get(&input, &["key"]).unwrap();
let cow: Cow<'_, str> = match lv.as_raw_cow() {
    Cow::Borrowed(s) if !s.contains('\\') => Cow::Borrowed(&s[1..s.len() - 1]),
    _ => Cow::Owned(lv.as_str().unwrap().to_string()),
};

It's a few more lines, but it keeps the library API lean and avoids the naming ambiguity.

if !s.contains('\\') => This will make it very slow in our case. It seems like a waste when the LazyValue itself already knows if it has escapes or not. Could you add a fn no_escaped() API at least?

I'd prefer to keep LazyValue's API surface small and let users compose what they need from the existing primitives. Thanks again for the PR though — I appreciate the thought you put into it!

I appreciate your burden. But this workaround is not zero-cost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants