# Snowbank Slices And Buffers

> How to correctly use the Slice type and its companions (SliceReader, SliceWriter, SliceOwner) for binary data in the FoundationDB .NET client / SnowBank.Core codebase. Slice is a readonly struct (namespace System) — the logical equivalent of a ReadOnlyMemory of bytes with many helpers. Use whenever code constructs or reads a Slice, converts between bytes and other types (Slice.FromBytes/FromStrin…

- **Type:** Skill
- **Install:** `agentstack add skill-snowbanksdk-foundationdb-dotnet-client-snowbank-slices-and-buffers`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [SnowBankSDK](https://agentstack.voostack.com/s/snowbanksdk)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** BSD-3-Clause
- **Upstream author:** [SnowBankSDK](https://github.com/SnowBankSDK)
- **Source:** https://github.com/SnowBankSDK/foundationdb-dotnet-client/tree/master/plugins/foundationdb-skills/skills/snowbank-slices-and-buffers
- **Website:** https://snowbanksdk.github.io/foundationdb-dotnet-client/

## Install

```sh
agentstack add skill-snowbanksdk-foundationdb-dotnet-client-snowbank-slices-and-buffers
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Slice, SliceReader, SliceWriter & friends

`Slice` is the workhorse for binary data in this codebase. It is a **`readonly struct`** (in namespace `System`) that wraps a segment of a `byte[]` — its three fields are `Array` (the backing array, possibly null), `Offset`, and `Count`. It predates `Span` and is the logical equivalent of **`ReadOnlyMemory`**, but with a large library of helpers for turning bytes into and out of real-world types. Keys and values in the FoundationDB binding are `Slice`s.

> **Two things to internalize first:** (1) a `Slice` is a **view**, not a copy — it shares the backing array. (2) `Slice.Nil` (no array) and `Slice.Empty` (zero-length array) are **different** and the distinction is load-bearing. Both are covered below.

For the Span-first equivalents (`SpanReader`/`SpanWriter`, `ISpanEncodable`) read [`references/span-readers-writers.md`](references/span-readers-writers.md); for pooled buffer-building (`ISliceBufferWriter`, `SlicePool`, `ValueBuffer`, allocators) read [`references/buffers-and-pooling.md`](references/buffers-and-pooling.md).

## 1. Nil vs Empty — the #1 gotcha

| | `Slice.Nil` | `Slice.Empty` |
|---|---|---|
| backing array | none (null-like) | a zero-length array |
| `IsNull` | `true` | `false` |
| `IsEmpty` | `false` | `true` |
| `IsNullOrEmpty` | `true` | `true` |
| `IsPresent` | `false` | `true` |
| `GetBytes()` | returns **`null`** | returns an **empty array** |
| `ToStringUtf8()` | returns **`null`** | returns **`""`** |
| `==` | `Nil != Empty` | distinct |
| `CompareTo` | `Nil` and `Empty` compare **equal** (both sort first) |

`tr.GetAsync(key)` returns **`Slice.Nil`** for a missing key, so the canonical "does it exist?" check is `value.IsNull` (or `IsNullOrEmpty` if an empty value also counts as absent). Use `Nil` to mean *absent* and `Empty` to mean *present but zero-length*.

```csharp
var v = await tr.GetAsync(key);
if (v.IsNull) { /* key does not exist */ }
```

## 2. Slice is a view — copy when you must own it

Constructing a `Slice` from a `byte[]` does **not** copy; the `Slice` references the array, so mutations to the array are visible through the slice (and its `.Span`). When you need an independent owner, copy:

```csharp
byte[] buf = ...;
var view = buf.AsSlice();        // shares buf — buf[i] = x is visible through view
byte[] mine = view.ToArray();    // defensive copy
buf[0] = 0xFF;                   // changes `view`, not `mine`
```

## 3. Constructing a Slice

```csharp
// from arrays / spans
byte[] b = ...;
b.AsSlice();                 b.AsSlice(offset, count);
new ArraySegment(b, o, n).AsSlice();
Slice.FromBytes("abc"u8);    // copies a ReadOnlySpan

// from text
Slice.FromStringUtf8("héllo");   Slice.FromString("héllo");   // UTF-8
Slice.FromStringAscii("ABC");    // ASCII only — lossy/throws on chars > 0x7F

// well-known
Slice.Empty;   Slice.Nil;   Slice.Zero(16);   // 16 zero bytes

// guids / uuids / hex
Slice.FromGuid(g);   Slice.FromUuid128(u);   Slice.FromHexString("00ff1234");
```

### Three integer encodings — pick deliberately

This is a classic source of bugs. They are **not** interchangeable:

| Factory | Encoding | Size (int32) | Read back with |
|---|---|---|---|
| `Slice.FromInt32(v)` | minimal little-endian (leading zero bytes dropped) | 1–4 bytes | `slice.ToInt32()` |
| `Slice.FromFixed32(v)` | fixed little-endian | always 4 bytes | `slice.ToInt32()` |
| `Slice.FromVarint32(v)` | 7-bit LEB128 varint | 1–5 bytes | (via `SliceReader.ReadVarInt32`) |

Every variant has a **big-endian** twin (`FromInt32BE`, `FromFixed32BE`, …) and 16/24/64/128-bit widths, plus floats (`FromSingle`/`FromDouble`) and `FromDecimal`. Big-endian fixed encodings are what you want when a number must **sort** correctly as a key. The minimal `FromInt32` is for standalone values you read whole with `ToInt32()` — it is *not* self-delimiting, so don't use it mid-stream (in a `SliceWriter`, use the fixed-width `WriteInt32`/`WriteInt64` or `WriteVarInt*` there; see §6).

> ⚠️ **Naming differs between `Slice` and the writer/reader.** On `Slice` (standalone), `FromFixed32` = 4 bytes and `FromInt32` = minimal. On `SliceWriter`/`SliceReader` (streams), the fixed-width method is plain **`WriteInt32`/`ReadInt32`** (4 bytes LE; `*BE` for big-endian), and the varint is **`WriteVarInt32`/`ReadVarInt32`**. (`WriteFixed32`/`ReadFixed32` exist but are `[Obsolete]` — use `WriteInt32`/`ReadInt32`.)

## 4. Reading values back

```csharp
slice.ToInt64();   slice.ToInt32BE();   slice.ToGuid();   slice.ToUuid128();
slice.ToStringUtf8();    // Nil -> null, Empty -> ""
slice.ToArray();         // defensive copy to byte[]
slice.ToHexString();

// zero-copy access to the bytes
ReadOnlySpan span = slice.Span;
ReadOnlyMemory mem = slice.Memory;

// slicing (negative indices count from the end)
slice.Substring(7, 6);   slice[2..5];   slice[^1..];
```

## 5. Comparison & equality

`Slice` compares **lexicographically by raw bytes** (the same order FoundationDB sorts keys), is offset/array-independent (equal content compares equal regardless of backing array or offset), and supports `==`, ``, `CompareTo`, `StartsWith`, `EndsWith`, `IndexOf`. For dictionaries/sorted sets, use `Slice.Comparer.Default` (an `IComparer` + `IEqualityComparer`).

```csharp
a.CompareTo(b) (Slice.Comparer.Default);
```

## 6. SliceWriter — build a buffer

`SliceWriter` is a **mutable, growable** builder (`struct`, `IBufferWriter`, `IDisposable`). Start from `default(SliceWriter)` (heap-backed, grows as needed) or `new SliceWriter(pool)` (rents from an `ArrayPool`):

```csharp
var w = new SliceWriter();
w.WriteInt32(42);                    // fixed 4 bytes LE  (self-delimiting)
w.WriteVarInt32(1000);               // LEB128            (self-delimiting)
w.WriteVarString("hello");           // length-prefixed UTF-8
w.WriteStringUtf8("raw");            // raw UTF-8, NO length prefix
w.WriteBytes(payload);               // append bytes
Slice result = w.ToSlice();          // the written region (a view into the writer's buffer)
```

- Use **self-delimiting** writes (fixed-width `WriteInt32`/`WriteInt64`/…, `WriteVarInt*`, `WriteVarString`) for anything you'll parse back sequentially. A raw `WriteStringUtf8`/`WriteBytes` has no length, so the reader must already know the length.
- `Position`, `Reset()`, `Rewind()`, `Skip(n)`, `Allocate(n)`/`AllocateSpan(n)` (reserve space to fill in place).
- **Pooling caveat:** if you pass an `ArrayPool`, you must either `Dispose()` the writer or hand the buffer off with `ToSliceOwner()` — otherwise the rented array is never returned. `ToSlice()` returns a *view into the writer's buffer*; if the writer (or its pooled buffer) is disposed/reused, that view becomes invalid — `ToArray()` or `ToSliceOwner()` it to keep it.

## 7. SliceReader — parse a buffer

`SliceReader` is a **forward cursor** over a `Slice`. Pair each read with the matching write:

```csharp
var r = result.ToSliceReader();
int n     = r.ReadInt32();           //  WriteInt32  (fixed 4 bytes)
uint k    = r.ReadVarInt32();        //  WriteVarInt32
string s  = r.ReadVarString();       //  WriteVarString
// raw / fixed-length string written without a prefix: read the known number of bytes
string raw = r.ReadBytes(3).ToStringUtf8();
Slice rest = r.ReadToEnd();
```

`Remaining`, `HasMore`, `Head` (bytes already read), `Tail` (bytes not yet read), and non-advancing `PeekByte()`/`PeekBytes(n)` round out the API. There is **no** `ReadStringUtf8(n)` — use `ReadBytes(n).ToStringUtf8()`.

## 8. SliceOwner — pooled, disposable Slices

`SliceOwner` is a rented `Slice` that returns its buffer to an `ArrayPool` on `Dispose` — the allocation-free analogue of `IMemoryOwner`. The contract: **you MUST `Dispose` it, and MUST NOT use its data afterward.**

```csharp
using (var owner = Slice.FromBytes(payload, ArrayPool.Shared))
{
    Slice data = owner.Data;     // valid only inside the using
    Use(data.Span);
}   // buffer returned to the pool here
```

`owner.IsValid`, `owner.Count`, `owner.Span`, `owner.Pool`; `SliceOwner.Wrap/Create/Copy` and `writer.ToSliceOwner()` produce them. Don't let an owner's `Data` escape the `using`.

## 9. Span / Memory interop & `ISpanEncodable`

`Slice` interops freely with the modern primitives: `slice.Span` (`ReadOnlySpan`), `slice.Memory` (`ReadOnlyMemory`), `byte[].AsSlice()`. Many hot types (keys, values, the writers) implement **`ISpanEncodable`** so they can be rendered into a caller's buffer with no intermediate `Slice` allocation — `TryGetSpan(out span)` / `TryGetSizeHint(out size)` / `TryEncode(dest, out written)`. That interface is how `subspace.Key(...)`/`FdbValue.*` write themselves into pooled buffers at the last moment.

For working directly over `Span` (a caller-owned, fixed buffer) instead of `Slice`, use `SpanReader`/`SpanWriter` — see [`references/span-readers-writers.md`](references/span-readers-writers.md).

## 10. Round-trip example

```csharp
// build
var w = new SliceWriter();
w.WriteInt32(order.Id);
w.WriteVarString(order.Customer);
w.WriteVarInt64(order.Total);
Slice packed = w.ToSlice();

// parse
var r = packed.ToSliceReader();
int id        = r.ReadInt32();
string cust   = r.ReadVarString();
long total    = (long) r.ReadVarInt64();
```

## 11. Self-check

- [ ] Did I use `IsNull`/`IsNullOrEmpty` (not `== Slice.Empty`) to test for a missing value?
- [ ] Am I treating `Slice` as a **view** — copying with `ToArray()`/`ToSliceOwner()` before mutating shared arrays or outliving a pooled buffer?
- [ ] Did I pick the right integer encoding (`Fixed*`/`*BE` for sortable keys; `VarInt*`/`Fixed*` for self-delimiting stream fields; `FromInt32` only for standalone whole-slice values)?
- [ ] Do my `SliceWriter` writes and `SliceReader` reads pair up (`WriteInt32`↔`ReadInt32`, `VarInt`↔`VarInt`, `VarString`↔`VarString`)?
- [ ] If I rented from an `ArrayPool` (`SliceWriter(pool)` / `SliceOwner`), did I `Dispose`/`ToSliceOwner()` so the buffer returns to the pool — and not use the data after disposal?

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [SnowBankSDK](https://github.com/SnowBankSDK)
- **Source:** [SnowBankSDK/foundationdb-dotnet-client](https://github.com/SnowBankSDK/foundationdb-dotnet-client)
- **License:** BSD-3-Clause
- **Homepage:** https://snowbanksdk.github.io/foundationdb-dotnet-client/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-snowbanksdk-foundationdb-dotnet-client-snowbank-slices-and-buffers
- Seller: https://agentstack.voostack.com/s/snowbanksdk
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
