What are you trying to do?
Use a generic type that implements Packable only under a trait bound as an Aztec note. Concretely, a wrapper like Foo<T> whose Packable impl is impl<T> Packable for Foo<T> where T: ToField + FromField. #[note] rejects the type even though every concrete instantiation that would actually be used as a note (e.g. Foo<Field>, since Field: ToField + FromField) satisfies the bound.
Real-world case: a PackableBoundedVec<T, MaxLen> (BoundedVec wrapper that is Packable only when T: ToField + FromField) — it cannot be used as / embedded generically in a note.
Code Reference
Nargo.toml:
[package]
name = "note_mre"
type = "contract"
authors = [""]
compiler_version = ">=1.0.0"
[dependencies]
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/aztec" }
src/main.nr:
use aztec::macros::aztec;
#[aztec]
pub contract NoteMre {
use aztec::macros::notes::note;
use aztec::protocol::traits::{FromField, Packable, ToField};
// `Foo<T>` is `Packable` only when `T` can round-trip through `Field`.
#[note]
pub struct Foo<T> {
pub inner: T,
}
impl<T> Packable for Foo<T>
where
T: ToField + FromField,
{
let N: u32 = 1;
fn pack(self) -> [Field; Self::N] {
[self.inner.to_field()]
}
fn unpack(packed: [Field; Self::N]) -> Self {
Foo { inner: T::from_field(packed[0]) }
}
}
}
aztec compile fails with:
error: Foo does not implement Packable trait. Either implement it manually or place #[derive(Packable)] on the note struct before #[note] macro invocation.
286 │ note.as_type().implements(packable_constraint),
(Foo<T> does implement Packable — just conditionally.)
Root cause
#[note]'s assert_has_packable in noir-projects/aztec-nr/aztec/src/macros/notes.nr does:
note.as_type().implements(packable_constraint)
For an unbounded generic whose Packable impl is conditional (where T: ToField + FromField), implements(...) returns false, so the note is rejected — even though it is Packable for every T that satisfies the bound.
The error message suggests #[derive(Packable)], but that does not apply here either: the derive emits impl<T> Packable for Foo<T> where T: Packable, whereas this type is packable via T: ToField + FromField, not T: Packable.
Expected behavior
#[note] (and ideally #[derive(Packable)]) should support generic types that are conditionally Packable: e.g. assert_has_packable should accept a conditional impl (and the generated NoteType/NoteHash impls should carry the same where bounds), so a bounded-generic type can be used as a note. Today the only workaround is to monomorphize at the note site (e.g. struct MyNote { foo: Foo<Field> }), which works because Field: ToField + FromField but prevents the generic itself from being a note.
Aztec Version
v4.2.0-aztecnr-rc.2 (noirc 1.0.0-beta.18)
OS
Linux
Node Version
v22.14.0
Proposed solutions
Option 1 — support generic type constraints on the struct definition.
Allow a where clause to be declared on the note struct (or honored from the existing conditional impl) and propagated to the macro-generated impls:
#[note]
struct Foo<T>
where
T: ToField + FromField,
{
inner: T,
}
assert_has_packable would then evaluate implements(Packable) under the declared bound (instead of on the unbounded generic), and the generated NoteType/NoteHash (and #[derive(Packable)]) impls would carry the same where T: ToField + FromField. This makes the bounded generic itself usable as a note.
Option 2 — support a newtype wrapper that transparently forwards the trait impls.
Provide a newtype/transparent-derive mechanism where a single-field wrapper delegates Packable (and the note's NoteType/NoteHash) to its inner field's impl:
#[note]
struct Foo(InnerPackable); // newtype; Packable derived by delegation to the inner field
The wrapper's Packable is obtained by forwarding to the inner type's existing impl, so #[note] never needs to reason about the inner type's generic bounds at all.
What are you trying to do?
Use a generic type that implements
Packableonly under a trait bound as an Aztec note. Concretely, a wrapper likeFoo<T>whosePackableimpl isimpl<T> Packable for Foo<T> where T: ToField + FromField.#[note]rejects the type even though every concrete instantiation that would actually be used as a note (e.g.Foo<Field>, sinceField: ToField + FromField) satisfies the bound.Real-world case: a
PackableBoundedVec<T, MaxLen>(BoundedVec wrapper that isPackableonly whenT: ToField + FromField) — it cannot be used as / embedded generically in a note.Code Reference
Nargo.toml:src/main.nr:aztec compilefails with:(
Foo<T>does implementPackable— just conditionally.)Root cause
#[note]'sassert_has_packableinnoir-projects/aztec-nr/aztec/src/macros/notes.nrdoes:For an unbounded generic whose
Packableimpl is conditional (where T: ToField + FromField),implements(...)returnsfalse, so the note is rejected — even though it isPackablefor everyTthat satisfies the bound.The error message suggests
#[derive(Packable)], but that does not apply here either: the derive emitsimpl<T> Packable for Foo<T> where T: Packable, whereas this type is packable viaT: ToField + FromField, notT: Packable.Expected behavior
#[note](and ideally#[derive(Packable)]) should support generic types that are conditionallyPackable: e.g.assert_has_packableshould accept a conditional impl (and the generatedNoteType/NoteHashimpls should carry the samewherebounds), so a bounded-generic type can be used as a note. Today the only workaround is to monomorphize at the note site (e.g.struct MyNote { foo: Foo<Field> }), which works becauseField: ToField + FromFieldbut prevents the generic itself from being a note.Aztec Version
v4.2.0-aztecnr-rc.2(noirc1.0.0-beta.18)OS
Linux
Node Version
v22.14.0
Proposed solutions
Option 1 — support generic type constraints on the struct definition.
Allow a
whereclause to be declared on the note struct (or honored from the existing conditional impl) and propagated to the macro-generated impls:assert_has_packablewould then evaluateimplements(Packable)under the declared bound (instead of on the unbounded generic), and the generatedNoteType/NoteHash(and#[derive(Packable)]) impls would carry the samewhere T: ToField + FromField. This makes the bounded generic itself usable as a note.Option 2 — support a newtype wrapper that transparently forwards the trait impls.
Provide a newtype/transparent-derive mechanism where a single-field wrapper delegates
Packable(and the note'sNoteType/NoteHash) to its inner field's impl:The wrapper's
Packableis obtained by forwarding to the inner type's existing impl, so#[note]never needs to reason about the inner type's generic bounds at all.