The heapless feature does not select allocation-free serialization
Description
With heapless enabled, human-readable serialization should use an allocation-free buffer; the serializer itself may still allocate. Instead, the following implementations can allocate while formatting:
SerializeforIpv4NetSerializeforIpv6NetSerializeforIpNet, which delegates to those implementations
The feature definition describes heapless as avoiding dynamic allocations during serialization. In the serialization implementation, the buffer path is selected by ser_as_str, while heapless alone uses collect_str.
Steps to Reproduce
Create a project with these files:
Cargo.toml
[package]
name = "ipnet-heapless-repro"
version = "0.1.0"
edition = "2021"
[workspace]
[features]
heapless = ["ipnet/heapless"]
legacy = ["ipnet/serde", "ipnet/ser_as_str"]
[dependencies]
ipnet = "=2.12.2"
serde = "=1.0.229"
serde_test = "=1.0.177"
allocation-counter = "=0.8.1"
heapless = "=0.9.2"
src/main.rs
use serde_test::{assert_ser_tokens, Configure, Token};
fn main() {
let net: ipnet::Ipv4Net = "255.255.255.255/32".parse().unwrap();
let info = allocation_counter::measure(|| {
assert_ser_tokens(&net.readable(), &[Token::Str("255.255.255.255/32")]);
});
assert_eq!(info.count_total, 0);
}
Run each command separately:
cargo +1.91.1 run --features heapless
cargo +1.91.1 run --features legacy
serde_test checks the exact string and its serialize_str does not allocate. The example measures serialization after parsing; the assertion expects zero allocations. legacy enables serde and ser_as_str as a control.
Expected Behavior
Both commands succeed because formatting uses an allocation-free buffer with this serializer.
Actual Behavior
The heapless command fails the assertion (three allocations in the observed run); the legacy command succeeds. Allocation counts can vary with the environment.
Environment
ipnet 2.12.2, serde 1.0.229, heapless 0.9.2, Rust 1.91.1, macOS arm64.
Change history
- v1 · created · 2026-10-28 13:36 UTC
{ "title": "The heapless feature does not select allocation-free serialization", "body": "## Description\n\nWith `heapless` enabled, human-readable serialization should use an allocation-free buffer; the serializer itself may still allocate. Instead, the following implementations can allocate while formatting:\n\n- `Serialize` for `Ipv4Net`\n- `Serialize` for `Ipv6Net`\n- `Serialize` for `IpNet`, which delegates to those implementations\n\nThe [feature definition](https://github.com/krisprice/ipnet/blob/13c5c5873c02127679b1e8e64b538e4e36942640/Cargo.toml#L18) describes `heapless` as avoiding dynamic allocations during serialization. In the [serialization implementation](https://github.com/krisprice/ipnet/blob/13c5c5873c02127679b1e8e64b538e4e36942640/src/ipnet_serde.rs), the buffer path is selected by `ser_as_str`, while `heapless` alone uses `collect_str`.\n\n## Steps to Reproduce\n\nCreate a project with these files:\n\n### `Cargo.toml`\n\n```toml\n[package]\nname = \"ipnet-heapless-repro\"\nversion = \"0.1.0\"\nedition = \"2021\"\n[workspace]\n[features]\nheapless = [\"ipnet/heapless\"]\nlegacy = [\"ipnet/serde\", \"ipnet/ser_as_str\"]\n[dependencies]\nipnet = \"=2.12.2\"\nserde = \"=1.0.229\"\nserde_test = \"=1.0.177\"\nallocation-counter = \"=0.8.1\"\nheapless = \"=0.9.2\"\n```\n\n### `src/main.rs`\n\n```rust\nuse serde_test::{assert_ser_tokens, Configure, Token};\n\nfn main() {\n let net: ipnet::Ipv4Net = \"255.255.255.255/32\".parse().unwrap();\n let info = allocation_counter::measure(|| {\n assert_ser_tokens(&net.readable(), &[Token::Str(\"255.255.255.255/32\")]);\n });\n assert_eq!(info.count_total, 0);\n}\n```\n\nRun each command separately:\n\n```sh\ncargo +1.91.1 run --features heapless\ncargo +1.91.1 run --features legacy\n```\n\n`serde_test` checks the exact string and its `serialize_str` does not allocate. The example measures serialization after parsing; the assertion expects zero allocations. `legacy` enables `serde` and `ser_as_str` as a control.\n\n## Expected Behavior\n\nBoth commands succeed because formatting uses an allocation-free buffer with this serializer.\n\n## Actual Behavior\n\nThe `heapless` command fails the assertion (three allocations in the observed run); the `legacy` command succeeds. Allocation counts can vary with the environment.\n\n## Environment\n\nipnet 2.12.2, serde 1.0.229, heapless 0.9.2, Rust 1.91.1, macOS arm64.", "validity": "reported", "upstream_status": "not_reported" } - v2 · link_added · 2026-10-28 13:51 UTC
{ "id": "dfd0e29a-c3f9-4af9-8668-dfcfe98f6288", "kind": "issue", "url": "https://github.com/krisprice/ipnet/issues/73", "version": null, "target_report_id": null, "target_revision_no": null, "edit_version": 1 }