-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Deque #807
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I really appreciate this PR. It's thorough, some important edge cases are covered, convention is followed, there's plenty of documentation, tests, and there's some clever stuff in there. Nice.
age: 123, | ||
}; | ||
|
||
// use it like a queue by pushing and popping at opposite ends |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice examples!
|
||
/// Helper method for `tail` and `head` methods to handle reading the value from storage | ||
fn read_meta_key(&self, storage: &dyn Storage, key: &[u8]) -> StdResult<u32> { | ||
let full_key = namespaces_with_key(&[self.namespace], key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using an existing helper that handles length encoding 👍
packages/storage-plus/src/deque.rs
Outdated
|
||
/// Tries to get the value at the given position | ||
/// Used internally | ||
fn get_at_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult<Option<T>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should we just name it
get_unchecked
? It feels like array accessors instd
are usually namedget
, notget_at
. - Since it costs us nothing, would you want to provide a public and checked
get
variant of this, and then call the typeVecDeque
, like you suggested before? I think it makes a lot of sense.
/// Adds the given value to the front of the deque | ||
pub fn push_front(&self, storage: &mut dyn Storage, value: &T) -> StdResult<()> { | ||
// need to subtract first, because head potentially points to existing element | ||
let pos = self.head(storage)?.wrapping_sub(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever. But I guess this effectively means there's a length limit to these deques, and once someone goes beyond that limit, there's undefined behavior. I feel this should be documented and/or we should guard the user from that. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the limit is u32::MAX
. We could guard against this when pushing by checking whether head and tail are equal and throwing an error. We will have to do an additional read from storage (of head or tail) for that though.
Which one do you prefer? Documenting or checking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you store the head and tail indexes under one storage key, this would become just one read.
Either way works for me. I guess if we document it and someone requests guards or bigger arrays, we can always add that later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like std::collections::VecDeque
will probably panic when grown beyond usize
. I think we're fine just documenting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Feel free to merge once it's up to date with main
and tests pass.
f83f2b8
to
db6cab2
Compare
Very nice. Looks like a JavaScript Array now. Can you explain the reasoning for renaming |
I suggested it. Fair point. I guess I got swayed by the fact it implements indices and random access. It isn't resizable though. Would you prefer something like |
I vote for just I think here is no need to add implementation details in the name itself. Rust tends to do that because it matters at the system language level and it allows them to communicate their version is not a linked list, which has certain overhead. |
#[cfg(feature = "iterator")] | ||
impl<'a, T> Iterator for VecDequeIter<'a, T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The range/iterator implementation is genius.
Do I understand correctly that this even works without the native iterator implementation? I think you can remove #[cfg(feature = "iterator")]
since you don't rely on storage ranges.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it only needs storage.get
. Actually, I was wondering about that iterator feature. Are there CosmWasm chains that don't support storage.range
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC yes, there are chains that don't support it due to how their storage works. I'm sure Simon or Ethan could elaborate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are underlying storage engines that don't have iterators. The default storage has it but e.g. Secret Network has an encrypted KV storage which you cannot iterate. Also Patricia Merkle Tries (Ethereum) do not support iterating.
So, should I make a follow-up PR renaming to |
That would be great! |
closes #776
Implements a deque, similar to the std lib
VecDeque
.This allows having both queue and stack semantics with a single data structure.
I used wrapping math, such that, in case the indexes ever reach
u32::MAX
or you always push to the front, it will still work (as long as it's not filled completely).No random access at the moment, but can add that, if desired.
The implementation is inspired by #786