Skip to content

Commit 5be8c8d

Browse files
docs(strings): Add ns_string documentation
- Added a doc about Small String Optimization (SSO) - Docs on how to initialize strings using `ns_string` - Docs on how to concatenate strings using `ns_string_concat()` function Signed-off-by: Vaishnav Sabari Girish <vaishnav.sabari.girish@gmail.com>
1 parent a48b832 commit 5be8c8d

8 files changed

Lines changed: 287 additions & 3 deletions

File tree

src/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
- [Creating & Freeing Strings](string/lifecycle.md)
2525
- [String Concatenation](string/concatenation.md)
2626

27+
- [Data Structures (`ns_data`)](data_structures/README.md)
28+
- [Dynamic Arrays (`ns_vec`)](data_structures/vectors.md)
29+
- [Key-Value Maps (`ns_hashmap`)](data_structures/hashmap.md)
30+
2731
- [Examples](examples/README.md)
2832
- [Safe User Input](examples/safe-input.md)
2933
- [Building a CLI Menu](examples/cli-menu.md)

src/data_structures/README.md

Whitespace-only changes.

src/data_structures/hashmap.md

Whitespace-only changes.

src/data_structures/vectors.md

Whitespace-only changes.

src/string/README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,42 @@
1-
# Strings (ns_string)
1+
# Strings (`ns_string`)
2+
3+
In standard C, a string is nothing more than a contiguous array of characters in
4+
memory, terminated by a null byte (`\0`).
5+
6+
While this design is incredibly minimalist, it is also the root cause of
7+
countless security vulnerabilities. Because standard C strings do not inherently
8+
know their own length or capacity, functions like `strcpy` and `strcat` will
9+
happily overwrite adjacent memory if the destination buffer is too small,
10+
leading to catastrophic **buffer overflows**.
11+
12+
Furthermore, simply finding the length of a standard C string using `strlen()`
13+
requires an `O(N)` operation—scanning every single byte until the null
14+
terminator is found.
15+
16+
## The NextStd Solution
17+
18+
The `ns_string` module introduces a radically different approach. Instead of raw
19+
character pointers, NextStd strings are intelligent structs managed safely by
20+
the Rust backend.
21+
22+
By tracking their own length and capacity, NextStd strings make out-of-bounds
23+
writes architecturally impossible. If you try to append text that exceeds the
24+
current capacity, the Rust backend will automatically and safely reallocate the
25+
heap memory for you.
26+
27+
Even better, `ns_string` implements **Small String Optimization (SSO)**. This
28+
means short strings avoid the slow, expensive heap allocation process entirely
29+
and are stored directly on the stack, delivering blistering performance that
30+
rivals or beats standard C arrays.
31+
32+
## What's in this chapter?
33+
34+
This chapter breaks down how to create, manage, and manipulate memory-safe
35+
strings:
36+
37+
- **[SSO Architecture](sso-architecture.md):** Look under the hood at how
38+
NextStd avoids heap allocations for short strings to maximize performance.
39+
- **[String Lifecycle](lifecycle.md):** Learn how to initialize, read, and
40+
safely free `ns_string` objects.
41+
- **[Concatenation & Manipulation](concatenation.md):** Discover how to safely
42+
append and modify strings without ever worrying about buffer overflows again.

src/string/concatenation.md

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,76 @@
1-
# String Concatenation
1+
# String Concatenation (`ns_string_concat`)
2+
3+
In standard C, appending text to a string using `strcat` is one of the most
4+
dangerous operations you can perform.
5+
6+
If the destination buffer doesn't have enough pre-allocated space to hold both
7+
the original string and the new text, `strcat` will silently overwrite adjacent
8+
memory. This leads to data corruption, mysterious crashes, and severe security
9+
vulnerabilities.
10+
11+
`NextStd` eliminates this entirely with memory-safe concatenation.
12+
13+
## Safe Concatenation
14+
15+
With `NextStd`, you never have to manually calculate buffer sizes. Instead of
16+
mutating an existing string, the `ns_string_concat` function takes two source
17+
`ns_string` objects and safely writes the combined text into a brand new
18+
destination `ns_string`.
19+
20+
Because combining two strings might require allocating memory on the heap (which
21+
can technically fail if the system is out of memory), it returns an `ns_error_t`
22+
that you should wrap in an `NS_TRY` block.
23+
24+
```c
25+
#include <nextstd/ns.h>
26+
27+
int main() {
28+
ns_error_t err;
29+
ns_string s1, s2, result;
30+
31+
// 1. Initialize the source strings
32+
NS_TRY(err, ns_string_new(&s1, "Hello, ")) {
33+
} NS_EXCEPT(err, NS_ERROR_ANY) { return 1; }
34+
35+
NS_TRY(err, ns_string_new(&s2, "memory safety!")) {
36+
} NS_EXCEPT(err, NS_ERROR_ANY) { return 1; }
37+
38+
// 2. Safely concatenate them into the 'result' string
39+
NS_TRY(err, ns_string_concat(&result, s1, s2)) {
40+
ns_println("Concatenation successful!");
41+
} NS_EXCEPT(err, NS_ERROR_ANY) {
42+
ns_print("Failed to concatenate: ");
43+
ns_println(ns_error_message(err));
44+
return 1;
45+
}
46+
47+
// 3. Print the combined result
48+
ns_print("Final message: ");
49+
ns_println(result);
50+
51+
// 4. Always clean up all strings!
52+
ns_string_free(&s1);
53+
ns_string_free(&s2);
54+
ns_string_free(&result);
55+
56+
return 0;
57+
}
58+
```
59+
60+
## How It Works (The SSO Transition)
61+
62+
When you call `ns_string_concat`, the Rust backend performs a series of safety
63+
checks to determine exactly how the new `result` string should be constructed:
64+
65+
1. **Capacity Calculation:** It adds the `len` of `s1` and `s2` together to find
66+
the exact required size.
67+
2. **The SSO Decision:** If the combined length is under 24 bytes, the `result`
68+
string will utilize **Small String Optimization (SSO)** and live entirely on
69+
the stack. Zero heap allocations occur!
70+
3. **Dynamic Reallocation:** If the combined length is 24 bytes or larger, the
71+
Rust backend safely allocates a new buffer on the heap, copies the data from
72+
both source strings into it, and flags the `result` struct as `is_heap`.
73+
74+
This entire memory management process happens invisibly. From the C side, you
75+
just call `ns_string_concat`, and `NextStd` guarantees it succeeds safely or
76+
returns a catchable error!

src/string/lifecycle.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,109 @@
1-
# Creating &amp; Freeing Strings
1+
# Creating & Freeing Strings
2+
3+
Unlike standard C strings, which are often just raw pointers to arbitrary blocks
4+
of memory, an `ns_string` is a fully managed data structure.
5+
6+
To guarantee memory safety and prevent undefined behavior, every `ns_string`
7+
must go through a strict, three-step lifecycle: **Initialization**, **Usage**,
8+
and **Destruction**.
9+
10+
## 1. Initialization (`ns_string_new`)
11+
12+
Before you can use an `ns_string`, it must be initialized. You do this by
13+
passing a pointer to your uninitialized `ns_string` struct and the initial
14+
standard C string (or `""` for an empty string) to the `ns_string_new` function.
15+
16+
This is where the Rust backend takes over. It calculates the length of your
17+
input, decides whether to store it on the stack (via Small String Optimization)
18+
or allocate memory on the heap, and populates your struct safely.
19+
20+
```c
21+
#include <nextstd/ns.h>
22+
23+
int main() {
24+
ns_error_t err;
25+
ns_string my_text;
26+
27+
// 1. Initialize the string
28+
NS_TRY(err, ns_string_new(&my_text, "Welcome to NextStd!")) {
29+
ns_println("String initialized successfully.");
30+
} NS_EXCEPT(err, NS_ERROR_ANY) {
31+
ns_println("Failed to allocate string.");
32+
return 1;
33+
}
34+
35+
// ... usage and destruction ...
36+
return 0;
37+
}
38+
```
39+
40+
## 2. Usage
41+
42+
Once initialized, your `ns_string` struct contains safely managed properties.
43+
Because it utilizes Small String Optimization (SSO), the underlying text data is
44+
safely tucked away inside a union.
45+
46+
Fortunately, you don't have to manually extract it. Because `ns_print` and
47+
`ns_println` handle `ns_string` objects natively via C11 `_Generic` macros, you
48+
can simply pass the entire struct directly to print your managed string!
49+
50+
```c
51+
// 2. Use the string
52+
ns_print("Message: ");
53+
ns_println(my_text); // No need to access inner pointers!
54+
```
55+
56+
## 3. Destruction (`ns_string_free`)
57+
58+
Standard C requires you to manually call `free()` on any memory you allocate. If
59+
you forget, your program leaks memory. If you call it twice, your program
60+
crashes (a "double free" vulnerability).
61+
62+
When you are finished with an `ns_string`, you **must** pass its pointer to
63+
`ns_string_free`.
64+
65+
```c
66+
// 3. Destroy the string
67+
ns_string_free(&my_text);
68+
```
69+
70+
The Rust backend handles the deallocation process intelligently. If the string
71+
was utilizing Small String Optimization (stored entirely on the stack),
72+
`ns_string_free` does practically nothing, safely avoiding an invalid heap
73+
operation. If it was allocated on the heap, Rust cleanly returns the memory to
74+
the system and nullifies the pointers to prevent accidental use-after-free
75+
vulnerabilities.
76+
77+
---
78+
79+
### The Complete Example
80+
81+
Putting it all together, here is the complete, memory-safe lifecycle of an
82+
`ns_string`:
83+
84+
```c
85+
#include <nextstd/ns.h>
86+
87+
int main() {
88+
ns_error_t err;
89+
ns_string greeting;
90+
91+
// 1. Initialization
92+
NS_TRY(err, ns_string_new(&greeting, "Hello, memory safety!")) {
93+
94+
// 2. Usage
95+
ns_print("The string is: ");
96+
ns_println(greeting); // Magically works!
97+
98+
} NS_EXCEPT(err, NS_ERROR_ANY) {
99+
ns_print("Error: ");
100+
ns_println(ns_error_message(err));
101+
return 1;
102+
}
103+
104+
// 3. Destruction (Always clean up!)
105+
ns_string_free(&greeting);
106+
107+
return 0;
108+
}
109+
```

src/string/sso-architecture.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,57 @@
11
# Small String Optimization (SSO)
2+
3+
In standard C, if you want a string that can grow dynamically, you have to use
4+
`malloc()` to allocate memory on the heap.
5+
6+
The problem is that heap allocations are notoriously slow. Requesting memory
7+
from the operating system, finding a contiguous block, and managing pointers
8+
adds significant overhead. If you are processing thousands of short strings—like
9+
parsing names, IP addresses, or configuration keys—this heap allocation
10+
bottleneck will severely degrade your application's performance.
11+
12+
NextStd solves this elegantly using a technique called
13+
**Small String Optimization (SSO)**.
14+
15+
## How SSO Works
16+
17+
The core idea behind SSO is simple: **Why ask the OS for memory if the string is
18+
small enough to fit inside the struct itself?**
19+
20+
When you initialize an `ns_string`, the Rust backend checks the length of the
21+
text you are trying to store.
22+
23+
1. **The Stack Path (Short Strings):** If the string is short (typically under
24+
15-23 characters, depending on the system architecture), NextStd stores the
25+
characters *directly inside* the `ns_string` struct on the stack. Zero heap
26+
allocations are performed.
27+
2. **The Heap Path (Long Strings):** If the string exceeds the inline capacity,
28+
NextStd automatically falls back to allocating space on the heap and stores a
29+
pointer to that data inside the struct.
30+
31+
## The Performance Impact
32+
33+
Because most strings in typical applications are short, SSO provides a massive
34+
performance boost:
35+
36+
* **Zero Allocation Cost:** Creating and destroying short strings is practically
37+
instantaneous.
38+
* **Cache Locality:** Because the string data lives directly inside the struct
39+
on the stack, the CPU can read it without having to jump across RAM to chase a
40+
heap pointer. This heavily optimizes CPU cache hits.
41+
* **Safe Fallback:** You never have to manually decide between a fixed-size
42+
stack array (which risks buffer overflows) and a dynamic heap pointer. The
43+
`ns_string` handles the transition invisibly.
44+
45+
## Invisible to the User
46+
47+
The best part about NextStd's SSO implementation is that you don't have to think
48+
about it.
49+
50+
Whether your string is 5 characters long and living on the stack, or 50,000
51+
characters long and living on the heap, the C API remains exactly the same. You
52+
still call `ns_string_new` to create it, read from its `data` pointer, and call
53+
`ns_string_free` when you are done.
54+
55+
The Rust backend tracks the memory state internally and guarantees that when
56+
`ns_string_free` is called, it won't accidentally try to `free()` a
57+
stack-allocated string, completely preventing invalid free crashes.

0 commit comments

Comments
 (0)