collections.chunk
Split a list into consecutive batches of at most n items, in order; the last batch may be short.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 11 tests, run in TypeScript, Python and Rust.
What it does
Splits a list into consecutive batches of `size` items: the shape an API with a "100 records per request" limit, a bulk insert or a mail-merge run needs. Every item appears in exactly one batch, in its original order, and only the last batch may be short.
An empty list gives no batches (`[]`), not one empty batch (`[[]]`), so `for batch in chunk(items, 100)` never sends an empty request.
For example
chunk(1, 2, 3, 4, 5, 6, 7, 3)→ ×3 seven items in threes leaves a short last batchchunk(1, 2, 3, 4, 5, 6, 3)→ ×2 a size that divides exactly leaves no short batchchunk(a, b, c, 1)→ ×3 size one gives one batch per item
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn chunk<T>(items: &[T], size: i64) -> Vec<Vec<T>>
| items | T[] | the list to split; it is not modified |
| size | int | the most items per batch, 1 or greater |
| returns | T[][] | batches in input order; an empty list gives no batches |
Your code names it in one line, in the file that uses it
fune!(collections.chunk@^1); // then call chunk(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
/// Split `items` into consecutive batches of at most `size`, in order.
///
/// An empty list gives no batches rather than one empty batch, so a loop over
/// the result never sends an empty request.
///
/// # Panics
/// Panics if `size` is less than 1: it is always a bug upstream, and "no
/// limit" is the outage batching prevents.
pub fn chunk<T: Clone>(items: &[T], size: i64) -> Vec<Vec<T>> {
if size < 1 {
panic!("size must be at least 1, received {}", size);
}
items.chunks(size as usize).map(|batch| batch.to_vec()).collect()
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
if let Value::Float(f) = args[1] {
if f.fract() != 0.0 {
panic!("size must be a whole number, received {}", f);
}
}
Value::Arr(
chunk(args[0].as_arr(), args[1].as_i64())
.into_iter()
.map(Value::Arr)
.collect(),
)
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add collections.chunk
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./collections.chunk-1.0.0-rust.fune, or fetch it from a terminal with fune pull collections.chunk@1.0.0:rust.
The whole function, every language, is one file too: collections.chunk-1.0.0.fune, 7,030 bytes, sha256 ac5a3b9d4cf08b46bee335755d23351d37b66124c4e76f71be2bf77ad356cf0e. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before collections.chunk
after — your function gets the result and the arguments, and returns the final result.
// fune: after collections.chunk
replace — it requires no other capability, so there is no dependency to replace.
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show collections.chunk --steps.
// fune: step collections.chunk after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| seven items in threes leaves a short last batch | 1, 2, 3, 4, 5, 6, 7, 3 | → | ×3 |
| a size that divides exactly leaves no short batch | 1, 2, 3, 4, 5, 6, 3 | → | ×2 |
| size one gives one batch per item | a, b, c, 1 | → | ×3 |
| a size larger than the list is one batch holding everything | a, b, 10 | → | ×1 |
| a size equal to the list is one full batch | a, b, c, 3 | → | ×1 |
| an empty list gives no batches, not one empty batch | , 5 | → | |
| records pass through untouched and in order | items ×3, 2 | → | ×2 |
| one short of a whole number of batches | 1, 2, 3, 4, 5, 2 | → | ×3 |
| size zero is an error, not 'no limit' | 1, 2, 3, 0 | → | error: size must be at least 1 |
| a negative size is an error | 1, 2, 3, -2 | → | error: size must be at least 1 |
Show the other 1 test
| Case | Arguments | Expected | |
|---|---|---|---|
| a fractional size is an error | 1, 2, 3, 1.5 | → | error: size must be a whole number |
More from the author
A `size` below 1 is an error rather than "everything in one batch": a zero or negative batch size is always a bug upstream, and quietly sending the whole list at once is the outage the batching was meant to prevent. A fractional size is an error too (TypeScript and Python; Rust's `i64` cannot express one).
A `size` larger than the list gives one batch holding everything.
The batches are new lists; the input is never modified. The items themselves are not copied, so objects in the batches are the same objects as in the input.
Files
| Path | Bytes |
|---|---|
| README.md | 947 |
| impl/python.py | 1,026 |
| impl/rust.rs | 1,096 |
| impl/typescript.ts | 858 |
| vectors.json | 1,378 |