ndslice field — Custom Iterators
mir.ndslice.field lets you build ndslice-compatible iterators over arbitrary memory layouts, computed values, or external data structures without copying.
Old URL: Previously at mir-algorithm.libmir.org/mir_ndslice_field.html. Redirects here.
A field in ndslice terminology is any type that satisfies the Field API: an opIndex(ptrdiff_t i) that returns an element. Fields are the lowest-level abstraction — Slice wraps a field (iterator) with shape and stride information.
Most users never write custom fields directly; they compose topology operations instead. Fields become useful when you need to:
- Wrap an external data buffer with a non-standard memory layout
- Expose a computed sequence as an ndslice
- Integrate with C libraries that provide pointer + stride
The Field interface
A field must implement:
struct MyField {
// Required: random access by linear index
auto ref opIndex(ptrdiff_t index);
// Optional but recommended: length
@property size_t length() const;
// Optional: slicing for sub-range views
auto opSlice(size_t i, size_t j);
}Built-in fields in mir.ndslice.field
IotaField — integer sequence
The field backing iota. Produces start + i on demand.
import mir.ndslice.field : IotaField;
import mir.ndslice.iterator : FieldIterator;
import mir.ndslice : slicedField;
auto field = IotaField!size_t(10); // starts at 10
auto s = field.slicedField(5); // [10, 11, 12, 13, 14]RepeatField — broadcast a value
import mir.ndslice.field : RepeatField;
auto rf = RepeatField!double(3.14);
// rf[any_index] == 3.14MapField — lazy element transform
The field backing topology.map. Wraps a field + a function.
import mir.ndslice.field : MapField;
// Usually created via map(), not directlyWriting a custom field
Example: a field that lazily reads rows from a memory-mapped file:
struct MMapField(T) {
ubyte* ptr;
size_t byteStride; // bytes between elements
T opIndex(ptrdiff_t i) const @nogc nothrow {
// Cast through byte pointer so arithmetic uses bytes, not sizeof(T)
return *(cast(T*)(ptr + i * byteStride));
}
}
// Wrap as ndslice — stride of 8 bytes between float elements
auto s = MMapField!float(cast(ubyte*) filePtr, 8)
.slicedField(1024); // 1024-element sliceslicedField — turning a field into a Slice
import mir.ndslice : slicedField;
auto myField = SomeField(...);
auto s1d = myField.slicedField(100); // 1-D
auto s2d = myField.slicedField(10, 10); // 2-DIterators vs Fields
| Concept | Role |
|---|---|
| Field | Zero-dimensional: field[i] → element |
| Iterator | One-dimensional: advance by 1 with ++, dereference with * |
| Slice | N-dimensional: wraps an iterator with shape + strides |
Topology operations like map and zip typically work at the iterator level. For custom layouts, fields are often simpler to implement.
ndslice topology — Lazy Views
mir.ndslice.topology provides zero-copy transformations of ndslice: reshape, transposed, diagonal, zip, iota, repeat, and more. All views are lazy — no data is copied.
Algorithms — map, reduce, each, fold
mir.algorithm.iteration provides functional-style algorithms that work natively with ndslice: each, reduce, fold, all, any, find, and more. All are @nogc and BetterC-compatible.