Skip to content

Borrow the newtype, not the representation, on primitive newtypes - #56

Merged
emilk merged 1 commit into
release-0.6from
emilk/primitive-newtype-ref
Aug 28, 2026
Merged

emilk merged 1 commit into
release-0.6from
emilk/primitive-newtype-ref

Conversation

@emilk

@emilk emilk commented Aug 28, 2026

Copy link
Copy Markdown
Member

Related

What

A primitive newtype reported two element types for the same column: as_slice() yielded &[ChunkId] (since #41) while column[i] yielded &[u8; 16], and #43's Deref then put first() (the newtype) next to get() (the representation).

For a Column<ChunkId>, where newtype_data_type!(ChunkId, FixedSizeBinary<16>, primitive):

Call Before After
column[i] &[u8; 16] &ChunkId
column.first() &ChunkId &ChunkId
column.as_slice() &[ChunkId] &[ChunkId]
column.value_owned(i) ChunkId ChunkId
column.value(i) &[u8; 16] &[u8; 16]
column.get(i) Option<&[u8; 16]> Option<&[u8; 16]>

So the change is RefType::Ref: indexing (column[i], and Index-based &column[i]) hands back the newtype, agreeing with as_slice, first(), and to_vec(). The primitive arm already requires Pod and layout compatibility, so this is sound the same way values() is: bytemuck::must_cast_ref on the representation's value_ref, size and alignment checked at compile time. Both macros get it — newtype_data_type! and try_newtype_data_type!.

value() / get() still hand out the representation's view. That is LogicalType::Value, which is load-bearing for the fallible arm, so it stays a separate question.

Testing

New assertions on the ChunkId fixture: array[1], as_slice()[1], and value_owned(1) agree on the newtype; value()/get() still give the representation; a plain (non-primitive) newtype still indexes as the representation. cargo test --all-features, cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check pass.

Compatibility

⚠️ Breaking, for primitive newtypes only, and only at column[i]:

let id: &[u8; 16] = &column[0]; // before
let id: &ChunkId  = &column[0]; // after — `&column[0].0` for the raw bytes

Code that already went through as_slice(), first(), to_vec(), or value_owned() is unaffected. Nothing new is rejected: a newtype that is not layout-compatible cannot be primitive in the first place.

🤖 Generated with Claude Code

`as_slice()` yields `&[ChunkId]` while `column[i]` yielded `&[u8; 16]`,
so the same column reported two element types depending on how it was
read — and `Deref` put `first()` (the newtype, through `as_slice`) right
next to `get()` (the representation).

The `primitive` arm already requires `Pod` and layout compatibility, so
`Ref = Self` is sound the same way `values()` is: `must_cast_ref` on the
representation's `value_ref`, size and alignment checked at compile time.

`value()` / `get()` still hand out the representation's view — that is
`LogicalType::Value`, load-bearing for the fallible arm, and a separate
question.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@emilk
emilk marked this pull request as ready for review August 28, 2026 10:09
@emilk
emilk merged commit 7f313c3 into release-0.6 Aug 28, 2026
6 checks passed
@emilk emilk mentioned this pull request Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant