| name | design_patterns-catgut_string |
| description | Use when working with catgut.String, encountering copy-detection panics, pooling strings, or needing mutable buffer-backed strings. Also applies when seeing noescape, copyCheck, catgut.GetPool, or MakeFromString in code.
|
| triggers | ["catgut","catgut.String","copyCheck","noescape","string interning","string pooling","MakeFromString"] |
Catgut Poolable String
Overview
catgut.String is a mutable, pool-managed string type backed by bytes.Buffer.
It replaces Go's immutable string in hot paths where allocation pressure from
temporary strings would be significant. Copy detection via self-pointer tracking
panics in debug builds when a catgut.String is accidentally copied by value.
Why It Exists
Go strings are immutable and heap-allocated. In dodder's inner loops — parsing
tags, paths, identifiers — creating many transient strings generates GC
pressure. catgut.String solves this by:
- Pooling: Buffers are reused across operations via
GetPool().GetWithRepool()
- Mutability:
Reset() clears contents without reallocating
- Copy protection: Debug panics prevent accidental value copies that would
break pool integrity
Type Definition
type String struct {
addr *String
data bytes.Buffer
}
Copy Detection
On every mutating operation, copyCheck() verifies the struct hasn't been
copied by value:
func (b *String) copyCheck() {
if b.addr == nil {
b.addr = (*String)(noescape(unsafe.Pointer(b)))
return
}
if b.addr != b {
panic("catgut: illegal use of non-zero String copied by value")
}
}
The noescape() function hides the pointer from Go's escape analysis to avoid
forcing heap allocation.
Pool Integration
func GetPool() interfaces.PoolPtr[String, *String] {
}
Usage:
str, repool := catgut.GetPool().GetWithRepool()
defer repool()
str.Set("hello")
For long-lived singletons:
var (
Blob, _ = catgut.MakeFromString("Blob")
Description, _ = catgut.MakeFromString("Description")
)
Key Methods
| Method | Purpose |
|---|
Set(string) | Set from Go string |
SetBytes([]byte) | Set from byte slice |
Reset() | Clear contents for reuse |
String() string | Convert to Go string |
Bytes() []byte | Access underlying bytes |
Len() int | Length |
IsEmpty() bool | Empty check |
Equals(*String) bool | Compare two catgut strings |
EqualsString(string) bool | Compare with Go string |
Compare(*String) cmp.Result | Three-way comparison |
Write([]byte) | Append bytes (implements io.Writer) |
WriteTo(io.Writer) | Stream contents out |
ReadFrom(io.Reader) | Read contents in |
Rules
- Always use as pointer (
*catgut.String). Never pass by value.
- Never assign one catgut.String to another. The copy detection will panic.
- Pool strings used in hot paths. Use
GetPool().GetWithRepool() and
defer repool().
- Use
Reset() before reuse, not reallocation.
Common Mistakes
| Mistake | Correct Approach |
|---|
Passing catgut.String by value | Always use *catgut.String |
Assigning a = b where both are catgut strings | Use a.SetBytes(b.Bytes()) |
| Allocating new strings in a loop | Pool with GetPool().GetWithRepool() and Reset() between iterations |
Ignoring repool from MakeFromString | Either defer repool() or discard with _ for process-lifetime singletons |