Repository navigation
refactor(string): remove ZigString public API, rename internal struct to StringView - #27473
Draft
dylan-conway wants to merge 7 commits into
Draft
dylan-conway wants to merge 7 commits into
dylan-conway wants to merge 7 commits into
Conversation
- Move bun.String.Slice struct definition into string.zig (was in ZigString.zig); ZigString.Slice is now an alias - Add toSyntaxErrorInstance() and toDOMExceptionInstance() to bun.String with C++ implementations - Route JSValue.put() with []const u8 keys through BunString path instead of ZigString - Add bun.String param type support to wrapInstanceMethod in host_fn.zig - Change NewRuntimeFunction/NewFunctionWithData to take ?*const bun.String - Update JSFFIFunction.cpp to take const BunString* (use toWTFString() copying variant since JSC stores the string long-term in NativeExecutable) - Update ffi.zig callers to use bun.String.init() - Update bindgen.ts to emit bun.String.static()
…ice + jest.zig static keys - Replace ZigString.Slice → bun.String.Slice across 48 files (165 line changes) - All variants: jsc.ZigString.Slice, JSC.ZigString.Slice, bun.ZigString.Slice, bun.jsc.ZigString.Slice - ZigString.Slice now only in string.zig (canonical) and string/wtf.zig (internal) - Replace all ZigString.static() → bun.String.static() in jest.zig (37 sites) - Removed now-unused ZigString import
…tring.static() in .put() keys Replace ZigString.static() with bun.String.static() for JSValue.put() keys across 43 files (~312 sites). JSValue.put() already supports bun.String keys, so this is purely mechanical. Remaining ZigString.static() occurrences (49 across 11 files) require different handling: - createObject2 args (*const ZigString signature) — Phase 2 FFI change - .toJS() / .toErrorInstance() chains — Pattern B, different replacement - .* dereferences — Phase 2 Also removed 10 now-unused 'const ZigString = jsc.ZigString' imports. Stats: - ZigString.static(): 398 → 49 (88% reduction) - Total ZigString in Zig: 1400 → 997 (29% reduction)
Phases 1.2 through 4 of the ZigString removal:
## Pattern conversions (~900+ call sites)
- Pattern B: ZigString.init().toJS() → bun.String.createUTF8ForJS()
- Changed ~30 function signatures from JSValue → bun.JSError!JSValue
- Pattern C: ZigString.init(msg).toErrorInstance() → bun.String.borrowUTF8/static().toErrorInstance()
- Empty strings: ZigString.init("").toJS() → jsEmptyString()
## FFI boundary conversions (30+ functions, ZigString* → BunString*)
- Output params (ref-counted): getClassName, getNameProperty, getSymbolDescription,
symbolKeyFor, getSourceCode → Bun::toStringRef(), Zig defer .deref()
- Input params (copying): createObject2, symbolFor, createAggregateError,
deleteModuleRegistryEntry, setTimeZone, deleteProperty → arg->toWTFString()
- Core: JSValue.toZigString → toBunString (borrowed .StringView tag)
- JSString.view now returns bun.String; toSlice → toUTF8WithoutRef
- FetchHeaders: append, get_, has, put, remove, fastGet_, createValue
- DOMURL: href_, pathname_ (ref-counted)
- DOMFormData: all functions + callbacks
- URLSearchParams: create, toString callback
- forEachProperty callbacks (3 variants)
- putRecord/fromEntries: []ZigString → []bun.String
- WebSocket: 14-param connect + writeString + close + didReceiveText
- Bun__Buffer_fill, Bun__Process__setCwd, Bun__getEnvValue
- Zig__GlobalObject__resolve queryString, ServerRouteList, CodeString
- wrapInstanceMethod: all html_rewriter.zig instance methods (27 fns),
CryptoHasher, EVP, server.publish, FFI.open
- Uses borrowed toString() (no ref overhead, matches old ZigString behavior)
## Deleted
- src/bun.js/bindings/ZigString.zig (745 lines)
- 13 ZigString__* C++ functions (bindings.cpp)
- bun.ZigString, jsc.ZigString public exports
- getZigString/putZigString deprecated wrappers
- jsc.ZigString case from wrapInstanceMethod/wrapStaticMethod
- ErrorableZigString type (unused)
- Dead: getCachedObject, putCachedObject, JSObject.getDirect/putDirect,
JSValue.createTypeError/createRangeError, JSValue.putRecord
- ~140 lines of dead Zig:: helpers
## Renamed
- Struct: ZigString → StringView (Zig) / ZigStringView (C++)
- Enum tags: .ZigString/.StaticZigString → .StringView/.StaticStringView
- Union field: BunStringImpl.zig → BunStringImpl.view
- String.toZigString() → private asStringViewUnsafe() (safety: borrows
WTFStringImpl bytes, dangerous if exposed)
- WTFStringImpl.toZigString() → asStringViewUnsafe()
- export ZigString__freeGlobal → BunString__freeGlobalBytes
- trait.isZigString() → trait.isStringSlice() (was unrelated naming collision)
## Perf fixes (restoring original behavior after initial conversion)
- FetchHeaders get/has/remove: toWTFString() → toWTFString(ZeroCopy)
(read-only lookups, safe to borrow)
- wrapInstanceMethod: toBunString() → toString() (borrowed, no ref overhead)
## Memory safety audit
- All INPUT conversions (Zig→C++): toWTFString() always copies for
.StringView tag — same or safer than old Zig::toString/toStringCopy
- All OUTPUT with toStringRef: explicit +1 ref, Zig derefs — safer than
old borrowed-raw-pointer approach
- All OUTPUT with toString/toStringView: borrowed, relies on source holding
ref (same invariant as old toZigString)
- Callbacks: borrowed .StringView tag on C++ stack, valid during callback
only — identical to old pattern
Net: 135 files, +1905/-2935 (-1030 lines)
Collaborator
|
Updated 4:15 AM PT - Feb 27th, 2026
❌ @dylan-conway, your commit 47cb5a3 has 5 failures in
🧪 To try this PR locally: bunx bun-pr 27473That installs a local version of the PR into your bun-27473 --bun |
…-splitting on semicolons The Buildkite build JSON response includes the commit message in the "message" field. When that message contains a semicolon (as it did in c6efe76: "JSString.view now returns bun.String; toSlice → ..."), passing ${BUILDKITE_BUILD} unquoted to string(JSON ...) causes CMake to split it into a list at the semicolon, truncating the JSON and producing "Syntax error: value, object or array expected." Quote all JSON variable references in string(JSON ...) calls to prevent this class of bug.
…b-oom test expectations Three fixes for CI failures on the ZigString removal branch: 1. BunString__toJSON: restore the oversized-string check that was in ZigString__toJSONObject. When a StringView exceeds the synthetic allocation limit, toWTFString() returns null — without this check we fell through to JSONParse(null) and threw a generic "Failed to parse JSON" instead of ERR_STRING_TOO_LONG. 2. blob-oom.test.ts: update expected error messages from "2^32-1" to the actual WTF::String::MaxLength (2147483647 = 2^31-1). The old hardcoded "2^32-1" message was always wrong; the new code path uses the correct limit from ErrorCode.cpp STRING_TOO_LONG. 3. node_fs_watcher.zig: remove duplicate "TODO: properly propagate" comment added during refactor (ban-words tracking). 4. ban-limits.json: globalThis.hasException 123 → 122 (dead code removed by refactor).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes the deprecated
ZigStringtype from the public API, migrating all callers tobun.String/BunString. The internal 16-byte{ptr, len}tagged-pointer struct remains as the backing storage for borrowed string views, but is renamed toStringView(Zig) /ZigStringView(C++) and made private.Net: −1,028 lines across 143 files (3 commits on top of
2222aa9f47)What changed
Deleted
src/bun.js/bindings/ZigString.zigZigString__*C++ FFI functions (bindings.cpp)bun.ZigString/jsc.ZigStringpublic exportsErrorableZigStringtype (unused)getCachedObject,putCachedObject,JSObject.getDirect/putDirect,JSValue.createTypeError/createRangeError,JSValue.putRecordjsc.ZigStringcase fromwrapInstanceMethod/wrapStaticMethodZig::helper functions (helpers.h)Renamed
ZigStringstructStringViewZigStringstructZigStringViewTag.ZigString/.StaticZigStringTag.StringView/.StaticStringViewBunStringImpl.zigBunStringImpl.viewString.toZigString()(public)asStringViewUnsafe()(private)ZigString__freeGlobalBunString__freeGlobalBytestrait.isZigString()trait.isStringSlice()Converted (~30 FFI functions, ~1000+ call sites)
Zig call-site patterns:
obj.put(g, ZigString.static("key"), val)obj.put(g, bun.String.static("key"), val)(~350 sites)ZigString.init(s).withEncoding().toJS(g)try bun.String.createUTF8ForJS(g, s)(~100 sites)ZigString.init(msg).toErrorInstance(g)bun.String.borrowUTF8(msg).toErrorInstance(g)(~50 sites)ZigString.Slicebun.String.Slice(~200 sites)ZigString.init("").toJS(g)jsEmptyString(g)FFI extern signatures (
ZigString*→BunString*):Bun::toStringRef):getClassName,getNameProperty,getSymbolDescription,symbolKeyFor,getSourceCode,DOMURL.pathname_Bun::toString/toStringView):JSValue.toBunString,JSString.toBunString,FetchHeaders.fastGet_/get_,DOMURL.href_toWTFString()):createObject2,symbolFor,createAggregateError,deleteModuleRegistryEntry,setTimeZone,deleteProperty,put,FetchHeaders.*,DOMFormData.*,URLSearchParams.*forEachProperty(3 variants),DOMFormData.forEach/toQueryString,URLSearchParams.toStringputRecord,fromEntries,ServerRouteList.createconnect+writeString+close+didReceiveTextBun__Buffer_fill,Bun__Process__setCwd,Bun__getEnvValue,resolvequeryString,CodeStringwrapInstanceMethod params (html_rewriter 27 methods, CryptoHasher, EVP, server.publish, FFI.open):
ZigString→bun.String, uses borrowedtoString()(no ref overhead, matching old behavior).Memory & performance audit
.ZigStringtag)Zig::toString()borrows latin1, copies UTF8toWTFString()always copiestoStringReftoZigString()borrows raw ptrBun::toStringRef()+1 ref, Zig derefstoString(no ref)toStringView.StringViewtag.put()static keystoIdentifier→ copy → atomizetoWTFString()→ copy → atomizeZig::toString()borrowtoWTFString(ZeroCopy)borrowgetZigString()borrow (0 ref overhead)toString()borrow (0 ref overhead).static().toJS()toStringCopycopiestoStringStatic→ASCIILiteralzero-copyTesting
bun run zig:check-all— 61/61 platforms passbun bd— compilesPre-existing failures (unchanged from main):
websocket-client.test.ts(needswsnpm pkg),resolve.test.ts(needstsyringe),expect.test.jsstress test timeout.Architecture
Before:
After:
The
.StringView/.StaticStringViewvariants mean "Zig-owned bytes, C++ must copy". The.WTFStringImplvariant means "JSC-owned, ref-counted".