Skip to content

Add collections crate#182

Open
febo wants to merge 1 commit intosolana-program:mainfrom
febo:collections-crate
Open

Add collections crate#182
febo wants to merge 1 commit intosolana-program:mainfrom
febo:collections-crate

Conversation

@febo
Copy link
Contributor

@febo febo commented Feb 25, 2026

Problem

Client generation need custom types to support String and Vec to have different prefix length types – e.g., a String with a u8 length prefix or a Vec<Address> without a prefix that consumes the remaining available bytes.

Solution

Add a new crate to include custom String and Vec types with borsh and wincode serialization support.

#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg(feature = "alloc")]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should depend on alloc by default so we can avoid having so many #[cfg(feature = "alloc")]. Currently both types require alloc.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine by me, we could even call the crate spl-alloc if we want to try something else 😅

@febo febo force-pushed the collections-crate branch from f48ca8f to 693a279 Compare February 25, 2026 18:40
@febo febo force-pushed the collections-crate branch from 693a279 to 83a6398 Compare February 25, 2026 18:42
@febo febo requested review from joncinque and lorisleiva February 25, 2026 18:43
@febo
Copy link
Contributor Author

febo commented Feb 25, 2026

The spl-collections name is experimental.

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Looks great overall! Mostly small points

#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg(feature = "alloc")]
Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine by me, we could even call the crate spl-alloc if we want to try something else 😅

[workspace]
resolver = "2"
members = [
"collections",
Copy link
Contributor

Choose a reason for hiding this comment

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

To go with these changes, can you include the new package at

SBPF_PROGRAM_PACKAGES: "['discriminator', 'generic-token', 'list-view', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']"
RUST_PACKAGES: "['discriminator', 'discriminator-derive', 'discriminator-syn', 'generic-token', 'generic-token-tests', 'list-view', 'pod', 'program-error', 'program-error-derive', 'tlv-account-resolution', 'type-length-value', 'type-length-value-derive', 'type-length-value-derive-test']"
WASM_PACKAGES: "['discriminator', 'generic-token', 'list-view', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']"
and ?

Comment on lines +237 to +239
while let Ok(item) = T::deserialize_reader(reader) {
items.push(item);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this only read prefix items and not all of them?


#[inline(always)]
fn size_of(src: &Self::Src) -> WriteResult<usize> {
Ok(core::mem::size_of::<$prefix_type>() + src.0.len())
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this multiply the length by the size of T?

Suggested change
Ok(core::mem::size_of::<$prefix_type>() + src.0.len())
Ok(core::mem::size_of::<$prefix_type>() + src.0.len() * core::mem::size_of::<T>())


while let Ok(item) = T::get(&mut reader) {
items.push(item);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same with this, shouldn't it only read prefix items?

Copy link
Contributor

Choose a reason for hiding this comment

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

Considering String just wraps Vec<u8> under the hood, maybe TrailingString can just wrap TrailingVec<u8> and PrefixedString can just wrap PrefixedVec<u8>, and we can remove a lot of this code, except for a converter to a String and a &str. But if it's too complicated, don't worry about it, this code looks fine to me. What do you think?

s.push_str(
core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| ErrorKind::InvalidData)?,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this approach fallible? If the reader happens to fill half-way through a multi-byte character, won't this fail? Or is that not possible because BUFFER_SIZE is a nice power of 2?

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.

2 participants