4.8 KiB
4.8 KiB
Task: Verify Generated Code Works in no_std / no_alloc
Goal: Ensure all generated protobuf code compiles and runs in no_std + no_alloc environments. Features requiring alloc must be gated behind feature flags in the proto generator's output.
Findings
Already clean (no_std + no_alloc):
- All reader structs (
Message<'a>) — zero-copy accessors over&[u8] - All field accessors, enums, builders (
MessageBuilder<'b>), typed iterators - Core runtime:
ProtoAccessor,ProtoBuilder,RevBuilder,read_varint,write_varint, all iterators
Needs alloc feature gate:
RotoMessagetrait — usesbytes::Bytes(hasArcinternally)RotoOwnedtrait — same issueOwnedMessagestructs generated by codegenRevBuilder::with()— usesVec<_>to collect raw fieldsBytes,BytesMutimports inDATA_IMPORTS
Needs std feature gate:
- All service code (
SERVICE_IMPORTS) — usesstd::sync::Arc,std::pin::Pin,String, etc. std::error::Error for RotoError(already gated)
Plan
Phase 1: Runtime (runtime/src/lib.rs, runtime/Cargo.toml)
- Gate
RotoMessageandRotoOwnedbehind#[cfg(feature = "alloc")] - Gate
BufMutBuilderbehind#[cfg(feature = "alloc")]— only useful withBytesMut, which requires alloc - Gate
use bytes::BufMut— only import it conditionally - Wire up
bytesfeatures in Cargo.toml:bytes = { version = "1.7", default-features = false } # ... [features] default = ["std", "alloc"] std = ["bytes/std"] alloc = ["bytes"]
Phase 2: Codegen — DATA_IMPORTS & Generated Data Code (codegen/src/generator/mod.rs)
-
Split
DATA_IMPORTSinto unconditional + gated sections:// Always emitted use roto_runtime::{ProtoAccessor, ProtoBuilder, RevBuilder, Result, RotoError, read_varint, RepeatedFieldIterator}; use core::str; use bytes::{Buf, BufMut}; // Only emitted when alloc feature is enabled #[cfg(feature = "alloc")] use bytes::{Bytes, BytesMut}; #[cfg(feature = "alloc")] use roto_runtime::RotoMessage; -
Gate
OwnedMessagestructs +RotoMessageimpls with#[cfg(feature = "alloc")] -
Fix
RevBuilder::with()— replacelet fields: Vec<_> = ... .collect()with a forward iteration that doesn't allocate. The reverse order doesn't matter for protobuf encoding (fields are independent):// Before: let fields: Vec<_> = msg.accessor.raw_fields().collect(); // for (raw, _) in fields.iter().rev() { ... } // After: for (raw, _) in msg.accessor.raw_fields() { ... }Alternatively, gate the
with()method itself behind#[cfg(feature = "alloc")].
Phase 3: Codegen — Service Code (codegen/src/generator/mod.rs)
- Gate
SERVICE_IMPORTSwith#[cfg(feature = "std")] - Gate all service traits, server impls, and
*Serverstructs with#[cfg(feature = "std")]
Phase 4: Generated Consumer Setup
- Emit a
Cargo.toml(or instruct consumers) with the right feature setup:[features] default = ["std"] std = ["roto-runtime/std", "alloc"] alloc = ["roto-runtime/alloc", "bytes"]
Phase 5: Validation
- Update
examples/no_std_testto build with--no-default-featuresand verify compilation - Run existing tests with default features to ensure nothing breaks
- Add a compile-test that verifies
--no-default-featuresbuilds succeed for generated code
Dependency Graph
no_std + no_alloc → readers, enums, builders, iterators
↓ alloc → + OwnedMessage, RotoMessage trait
↓ std → + service code, std::error::Error
Risk Assessment
| Risk | Mitigation |
|---|---|
Breaking existing consumers who use OwnedMessage |
OwnedMessage was already behind a manual #[cfg] in the no_std_test example — consumers aren't relying on it unconditionally |
BufMutBuilder removal in no-alloc mode |
It's only useful with BytesMut anyway, which requires alloc |
RevBuilder::with() change |
Forward iteration is semantically equivalent for protobuf encoding |
Files to Modify
| File | Changes |
|---|---|
runtime/src/lib.rs |
Gate RotoMessage, RotoOwned, BufMutBuilder, use bytes::BufMut |
runtime/Cargo.toml |
Wire alloc → bytes dependency, add bytes/std to std feature |
codegen/src/generator/mod.rs |
Split DATA_IMPORTS, gate OwnedMessage, gate service code, fix RevBuilder::with() |
examples/no_std_test/ |
Update to use new feature structure |
Execution Order
- Phase 1 (runtime) — foundation
- Phase 2 (codegen data imports) — depends on runtime
- Phase 3 (codegen services) — independent
- Phase 4–5 (consumer setup + validation) — depends on all above