Derive Debug, Clone, Copy, PartialEq, Eq for the column descriptors - #32
Conversation
`DynColumnDesc` derives `Clone, Copy, Debug, Eq, PartialEq`. `ColumnDesc` gets them hand-written: `#[derive]` would put each trait's bound on `L`, but a descriptor holds no `L` — only a `PhantomData<fn() -> L>`, which is `Copy`, `Eq`, and `Debug` whatever `L` is. `Debug` skips the `PhantomData`. `to_dyn` now takes `self` by value, since `Self: Copy`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uses a `ColumnDesc<Utf8>` for the `Debug` and `Eq` checks: `Utf8` itself derives nothing, so those lines only compile because the impls put no bound on `L`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Post-merge review, as part of reviewing the 0.6 stack for #30. Clippy and tests are clean at the tip of release-0.6. Hand-writing the four impls is the right call, and using ColumnDesc<Utf8> in the test — where Utf8 derives nothing — is a compile-time proof that the impls carry no bound on L, which no runtime assertion could give you.
PartialEq on the metadata disagrees with arrow_field() equality. metadata is semantically a map but is compared as an ordered slice. Verified at the tip of release-0.6:
const A: ColumnDesc<Utf8> = ColumnDesc::new_with_metadata("R", "c", &[("a", "1"), ("b", "2")]);
const B: ColumnDesc<Utf8> = ColumnDesc::new_with_metadata("R", "c", &[("b", "2"), ("a", "1")]);
A == B // false
A.arrow_field() == B.arrow_field() // trueDuplicate keys go the other way: &[("a", "1"), ("a", "2")] yields arrow_metadata() == {"a": "2"} while PartialEq sees two entries. So descriptors producing byte-identical arrow fields can compare unequal, and vice versa. The derive emits attribute order so only hand-written descriptors bite — which is the audience #23/#24 opened the door for. Either compare as maps (sorted pairs, no allocation, they are &'static) or document that the metadata is compared as written.
Eq without Hash. The obvious use for Eq on a descriptor is keying a map ("which columns have I handled"), and that needs Hash. Three lines, no bound on L. It has to match whatever the metadata comparison above settles on, which is the usual reason to sort that question first.
Minor: Copy makes DynColumnDesc::from(desc) (no &) the natural spelling, and it still doesn't compile — only From<&ColumnDesc<L>> exists.
— Claude
Related
What
DynColumnDescderivesClone, Copy, Debug, Eq, PartialEq.ColumnDescgets the same set hand-written, because#[derive]would put each trait's bound onL. A descriptor holds noL— only aPhantomData<fn() -> L>, which isCopy,Eq, andDebugwhateverLis.Debugskips thePhantomDataand prints just the three real fields.to_dynnow takesselfby value, sinceSelf: Copy(clippy'swrong_self_conventionasks for it).desc.to_dyn()is unchanged at the call site.One thing worth checking against your use case: the issue's motivating example compares descriptors from two different structs, so
record_typediffers and full-struct equality would befalsewhere the current.name == .nameistrue. Comparing whole descriptors is the stricter check, not a drop-in for that assertion. Say the word if you want a name-and-type-only comparison instead of (or alongside)PartialEq.Testing
New test covers
Copy, both equality directions including metadata taking part, and thatDebugnames the fields without leakingPhantomData. It uses aColumnDesc<Utf8>, andUtf8derives nothing, so those lines only compile if the impls carry no bound onL.Compatibility
Additive, except
to_dyntakingself, which only affects explicitColumnDesc::to_dyn(&x)calls.to_dynis unreleased.🤖 Generated with Claude Code