Skip to content

collections: write wincode vec elements via schema to match reader#197

Merged
febo merged 2 commits into
solana-program:mainfrom
plutohan:plutohan/issue-196-vec-schema-write
Jul 7, 2026
Merged

collections: write wincode vec elements via schema to match reader#197
febo merged 2 commits into
solana-program:mainfrom
plutohan:plutohan/issue-196-vec-schema-write

Conversation

@plutohan

@plutohan plutohan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #196.

The wincode SchemaWrite impls for TrailingVec and the prefixed-vec wrappers wrote elements as a raw byte copy sized by size_of::<T>(), while the read side decodes each element via its schema. For non-POD T those sizes disagree and the parse boundary drifts, so the field after the vector decodes wrong.

Both impls now write and size each element through SchemaWrite, matching the reader. No wire-format change for POD types. Added non-POD round-trip regression tests with a trailing field to pin the parse boundary.

@joncinque joncinque requested a review from febo July 2, 2026 11:11
@joncinque

Copy link
Copy Markdown
Contributor

@febo can you take a look?

@febo

febo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for the PR @plutohan! I left a comment on the issue since supporting non-pod types might affect how JS handles these types. Let's wait to see what @lorisleiva says about it.

@febo febo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Just left a few nits.

Comment thread collections/src/vec.rs Outdated
Comment on lines +145 to +150
// Sum the serialized size of each element, matching the per-element
// decoding performed by the read side.
let mut expected_size = 0usize;
for item in src.0.iter() {
expected_size = expected_size.saturating_add(<T as SchemaWrite<C>>::size_of(item)?);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We could use try_fold here.

Suggested change
// Sum the serialized size of each element, matching the per-element
// decoding performed by the read side.
let mut expected_size = 0usize;
for item in src.0.iter() {
expected_size = expected_size.saturating_add(<T as SchemaWrite<C>>::size_of(item)?);
}
// Sum the serialized size of each element, matching the per-element
// decoding performed by the read side.
let expected_size = src
.0
.iter()
.try_fold(0usize, |size, item| -> WriteResult<usize> {
Ok(size.saturating_add(<T as SchemaWrite<C>>::size_of(item)?))
})?;

Comment thread collections/src/vec.rs Outdated
Comment on lines 166 to 171
for item in src.0.iter() {
<T as SchemaWrite<C>>::write(&mut writer, item)?;
}

Ok(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same here, we can use try_for_each.

Suggested change
for item in src.0.iter() {
<T as SchemaWrite<C>>::write(&mut writer, item)?;
}
Ok(())
}
src.0
.iter()
.try_for_each(|item| T::write(&mut writer, item))
}

Comment thread collections/src/vec.rs Outdated
Comment on lines +277 to +281
let mut expected_size = core::mem::size_of::<$prefix_type>();
for item in src.0.iter() {
expected_size = expected_size
.saturating_add(<T as SchemaWrite<C>>::size_of(item)?);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same as above, try_fold.

Suggested change
let mut expected_size = core::mem::size_of::<$prefix_type>();
for item in src.0.iter() {
expected_size = expected_size
.saturating_add(<T as SchemaWrite<C>>::size_of(item)?);
}
let expected_size = src
.0
.iter()
.try_fold(core::mem::size_of::<$prefix_type>(), |size, item| -> WriteResult<usize> {
Ok(size.saturating_add(<T as SchemaWrite<C>>::size_of(item)?))
})?;

Comment thread collections/src/vec.rs Outdated
Comment on lines +302 to +306
for item in src.0.iter() {
<T as SchemaWrite<C>>::write(&mut writer, item)?;
}

Ok(())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same as above, try_for_each.

Suggested change
for item in src.0.iter() {
<T as SchemaWrite<C>>::write(&mut writer, item)?;
}
Ok(())
src.0
.iter()
.try_for_each(|item| T::write(&mut writer, item))

@plutohan

plutohan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Applied, thanks!

@febo febo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks!

@febo febo merged commit 803bf0c into solana-program:main Jul 7, 2026
53 checks passed
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.

spl-collections: wincode Vec wrappers write raw bytes but read via schema

3 participants