From 373589324070bad1d6ef7313ea7f337ad1ce3dc7 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 24 Jan 2019 19:29:31 +0100 Subject: [PATCH 1/4] Add a large number of unit tests --- src/tests.rs | 569 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 514 insertions(+), 55 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index e025349..7872d31 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,82 +1,472 @@ use super::StableVec; -quickcheck! { - fn reordering_compact(insertions: u16, to_delete: Vec) -> bool { - let insertions = insertions + 1; - // Create stable vector containing `insertions` zeros. Afterwards, we - // remove at most half of those elements - let mut sv = StableVec::from(vec![0; insertions as usize]); - for i in to_delete { - let i = (i % insertions) as usize; - if sv.has_element_at(i) { - sv.remove(i); - } +macro_rules! assert_panic { + ($($body:tt)*) => {{ + let res = std::panic::catch_unwind(|| { + $($body)* + }); + if let Ok(x) = res { + panic!( + "expected panic for '{}', but got '{:?}' ", + stringify!($($body)*), + x, + ); } + }} +} - // Remember the number of elements before and call compact. - let sv_before = sv.clone(); - let n_before_compact = sv.num_elements(); - sv.reordering_make_compact(); +macro_rules! assert_sv_eq { + ($left:expr, [$(; $last_index:literal)*]: $ty:ty $(,)*) => {{ + let sv = &mut $left; - n_before_compact == sv.num_elements() - && sv.is_compact() - && (0..n_before_compact).all(|i| sv.get(i).is_some()) - && sv_before.iter().all(|e| sv.contains(e)) - } + let last_index = 0 $(+ $last_index)*; + let next_index = if last_index == 0 { 0 } else { last_index + 1 }; - fn compact(insertions: u16, to_delete: Vec) -> bool { - let insertions = insertions + 1; - // Create stable vector containing `insertions` zeros. Afterwards, we - // remove at most half of those elements - let mut sv = StableVec::from(vec![0; insertions as usize]); - for i in to_delete { - let i = (i % insertions) as usize; - if sv.has_element_at(i) { - sv.remove(i); + assert_eq!(sv.num_elements(), 0); + assert!(sv.is_empty()); + assert_eq!(sv.is_compact(), next_index == 0); + assert_eq!(sv.next_index(), next_index); + assert!(sv.capacity() >= next_index); + + assert_eq!(sv.iter().count(), 0); + assert_eq!(sv.iter_mut().count(), 0); + assert_eq!((&*sv).into_iter().count(), 0); + assert_eq!((&mut *sv).into_iter().count(), 0); + assert_eq!(sv.keys().count(), 0); + + assert_eq!(sv, &[] as &[$ty]); + assert_eq!(sv, &vec![] as &Vec<$ty>); + + assert_eq!(format!("{:?}", sv), "StableVec []"); + assert!(sv.clone().into_vec().is_empty()); + }}; + ($left:expr, [$( $idx:literal => $val:expr ),* $(; $last_index:literal)*] $(,)*) => {{ + let sv = &mut $left; + + let indices = [$($idx),*]; + let mut values = [$($val),*]; + let num_elements = indices.len(); + let last_index = 0 $(+ $last_index)*; + let last_index = if last_index == 0 { + *indices.last().unwrap() + } else { + last_index + }; + + assert_eq!(sv.num_elements(), num_elements); + assert_eq!(sv.is_empty(), num_elements == 0); + assert_eq!(sv.is_compact(), last_index + 1 == num_elements); + assert_eq!(sv.next_index(), last_index + 1); + assert!(sv.capacity() >= last_index + 1); + + assert_eq!(sv.iter().cloned().collect::>(), values); + assert_eq!(sv.iter_mut().map(|r| *r).collect::>(), values); + assert_eq!((&*sv).into_iter().cloned().collect::>(), values); + assert_eq!((&mut *sv).into_iter().map(|r| *r).collect::>(), values); + assert_eq!(sv.keys().collect::>(), indices); + + let expected_hint = (num_elements, Some(num_elements)); + assert_eq!(sv.iter().cloned().size_hint(), expected_hint); + assert_eq!(sv.iter_mut().map(|r| *r).size_hint(), expected_hint); + assert_eq!((&*sv).into_iter().cloned().size_hint(), expected_hint); + assert_eq!((&mut *sv).into_iter().map(|r| *r).size_hint(), expected_hint); + assert_eq!(sv.keys().size_hint(), expected_hint); + + assert_eq!(sv, &values as &[_]); + assert_eq!(sv, &values.to_vec()); + + assert_eq!(format!("{:?}", sv), format!("StableVec {:?}", values)); + assert_eq!(sv.clone().into_vec(), values); + + for i in 0..last_index { + if let Ok(index_index) = indices.binary_search(&i) { + assert!(sv.has_element_at(i)); + assert_eq!(sv.get(i), Some(&values[index_index])); + assert_eq!(sv.get_mut(i), Some(&mut values[index_index])); + assert_eq!(sv[i], values[index_index]); + } else { + assert!(!sv.has_element_at(i)); + assert_eq!(sv.get(i), None); + assert_eq!(sv.get_mut(i), None); + assert_panic!(sv[i]); } } + }}; +} - // Remember the number of elements before and call compact. - let sv_before = sv.clone(); - let items_before: Vec<_> = sv_before.iter().cloned().collect(); - let n_before_compact = sv.num_elements(); - sv.make_compact(); +#[test] +fn new() { + let mut sv: StableVec = StableVec::new(); + assert_sv_eq!(sv, []: String); +} +#[test] +fn default() { + let mut sv: StableVec = StableVec::default(); + assert_sv_eq!(sv, []: String); +} - n_before_compact == sv.num_elements() - && sv.is_compact() - && (0..n_before_compact).all(|i| sv.get(i).is_some()) - && sv == items_before +#[test] +fn with_capacity() { + let mut sv: StableVec = StableVec::with_capacity(3); + + assert!(sv.capacity() >= 3); + assert_sv_eq!(sv, []: String); +} + +#[test] +fn reserve() { + let mut sv: StableVec = StableVec::new(); + + // Reserve for 5 + sv.reserve(5); + assert!(sv.capacity() >= 5); + assert_sv_eq!(sv, []: String); + + // Reserve for 2 more + sv.reserve(7); + assert!(sv.capacity() >= 7); + assert_sv_eq!(sv, []: String); + + // Reserving for 6 should do nothing because we already have memory for 7 + // or more! + let cap_before = sv.capacity(); + sv.reserve(6); + assert_eq!(sv.capacity(), cap_before); + assert_sv_eq!(sv, []: String); + + // After pushing 23 elements, we should have at least memory for 23 items. + for _ in 0..23 { + sv.push("x".into()); } + assert!(sv.capacity() >= 23); - fn from_and_extend_and_from_iter(items: Vec) -> bool { - use std::iter::FromIterator; + // Reserving for 13 more elements + sv.reserve(13); + assert!(sv.capacity() >= 36); - let iter_a = items.iter().cloned(); - let iter_b = items.iter().cloned(); + // Reserving for 2 more shouldn't do anything because we already reserved + // for 13 additional ones. + let cap_before = sv.capacity(); + sv.reserve(2); + assert_eq!(sv.capacity(), cap_before); +} - let sv_a = StableVec::from_iter(iter_a); - let sv_b = { - let mut sv = StableVec::new(); - sv.extend(iter_b); - sv - }; - let sv_c = StableVec::from(&items); +#[test] +fn from_vec() { + assert_sv_eq!( + StableVec::::from_vec(vec![]), + []: String, + ); - sv_a.num_elements() == items.len() - && sv_a == sv_b - && sv_a == sv_c + assert_sv_eq!( + StableVec::from_vec(vec![1]), + [0 => 1], + ); + + assert_sv_eq!( + StableVec::from_vec(vec![2, 9, 5]), + [0 => 2, 1 => 9, 2 => 5], + ); +} + +#[test] +fn from() { + assert_sv_eq!( + StableVec::::from(&[]), + []: String, + ); + + assert_sv_eq!( + StableVec::from(&[1]), + [0 => 1], + ); + + assert_sv_eq!( + StableVec::from(&[2, 9, 5]), + [0 => 2, 1 => 9, 2 => 5], + ); +} + +#[test] +fn push_simple() { + let mut sv = StableVec::new(); + + sv.push('a'); + assert_sv_eq!(sv, [0 => 'a']); + + sv.push('b'); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b']); + + sv.push('c'); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b', 2 => 'c']); +} + +#[test] +fn pop_simple() { + let mut sv = StableVec::from_vec(vec!['a', 'b', 'c']); + + assert_eq!(sv.pop(), Some('c')); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b'; 2]); + + assert_eq!(sv.pop(), Some('b')); + assert_sv_eq!(sv, [0 => 'a'; 2]); + + sv.push('d'); + assert_sv_eq!(sv, [0 => 'a', 3 => 'd']); + + sv.push('e'); + assert_sv_eq!(sv, [0 => 'a', 3 => 'd', 4 => 'e']); + + assert_eq!(sv.pop(), Some('e')); + assert_sv_eq!(sv, [0 => 'a', 3 => 'd'; 4]); + + assert_eq!(sv.pop(), Some('d')); + assert_sv_eq!(sv, [0 => 'a'; 4]); + + assert_eq!(sv.pop(), Some('a')); + assert_sv_eq!(sv, [; 4]: char); +} + +#[test] +fn grow() { + let mut sv = StableVec::from_vec(vec!['a', 'b']); + + sv.grow(1); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b'; 2]); + + sv.grow(9); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b'; 11]); +} + +#[test] +fn remove() { + let mut sv = StableVec::from_vec(vec!['a', 'b', 'c']); + + assert_eq!(sv.remove(1), Some('b')); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c']); + + assert_eq!(sv.remove(3), None); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c']); + + sv.extend_from_slice(&['d', 'e']); + assert_eq!(sv.remove(4), Some('e')); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c', 3 => 'd'; 4]); + + assert_eq!(sv.remove(4), None); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c', 3 => 'd'; 4]); + + assert_eq!(sv.remove(5), None); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c', 3 => 'd'; 4]); + + assert_eq!(sv.remove(1), None); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c', 3 => 'd'; 4]); + + assert_eq!(sv.remove(0), Some('a')); + assert_sv_eq!(sv, [2 => 'c', 3 => 'd'; 4]); +} + +#[test] +fn insert_into_hole() { + let mut sv = StableVec::from_vec(vec!['a', 'b', 'c']); + + assert_eq!(sv.insert_into_hole(1, 'x'), Err('x')); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b', 2 => 'c']); + + assert_eq!(sv.insert_into_hole(3, 'x'), Err('x')); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b', 2 => 'c']); + + assert_eq!(sv.remove(1), Some('b')); + assert_eq!(sv.insert_into_hole(1, 'd'), Ok(())); + assert_sv_eq!(sv, [0 => 'a', 1 => 'd', 2 => 'c']); + + assert_eq!(sv.insert_into_hole(1, 'x'), Err('x')); + assert_sv_eq!(sv, [0 => 'a', 1 => 'd', 2 => 'c']); + + assert_eq!(sv.remove(1), Some('d')); + assert_sv_eq!(sv, [0 => 'a', 2 => 'c']); + + assert_eq!(sv.insert_into_hole(1, 'e'), Ok(())); + assert_sv_eq!(sv, [0 => 'a', 1 => 'e', 2 => 'c']); + + sv.grow(2); + assert_eq!(sv.insert_into_hole(5, 'x'), Err('x')); + assert_sv_eq!(sv, [0 => 'a', 1 => 'e', 2 => 'c'; 4]); + + assert_eq!(sv.insert_into_hole(4, 'f'), Ok(())); + assert_sv_eq!(sv, [0 => 'a', 1 => 'e', 2 => 'c', 4 => 'f']); + + assert_eq!(sv.insert_into_hole(3, 'g'), Ok(())); + assert_sv_eq!(sv, [0 => 'a', 1 => 'e', 2 => 'c', 3 => 'g', 4 => 'f']); +} + +#[test] +fn clear() { + let mut sv: StableVec = StableVec::new(); + sv.clear(); + assert_sv_eq!(sv, []: String); + + let mut sv = StableVec::from_vec(vec![1, 3, 5]); + sv.clear(); + assert_sv_eq!(sv, []: u32); +} + +#[test] +fn extend_from_slice() { + let mut sv = StableVec::new(); + + sv.extend_from_slice(&['a']); + assert_sv_eq!(sv, [0 => 'a']); + + sv.push('b'); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b']); + + sv.extend_from_slice(&['c', 'd']); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd']); + + assert_eq!(sv.pop(), Some('d')); + sv.extend_from_slice(&['e']); + assert_sv_eq!(sv, [0 => 'a', 1 => 'b', 2 => 'c', 4 => 'e']); +} + +#[test] +fn write() { + use std::io::Write; + + let mut sv = StableVec::new(); + + sv.write_all(&[0, 7, 3]).unwrap(); + assert_sv_eq!(sv, [0 => 0, 1 => 7, 2 => 3]); + + sv.pop(); + sv.write_all(&[4, 8]).unwrap(); + assert_sv_eq!(sv, [0 => 0, 1 => 7, 3 => 4, 4 => 8]); + + sv.write_all(&[5]).unwrap(); + assert_sv_eq!(sv, [0 => 0, 1 => 7, 3 => 4, 4 => 8, 5 => 5]); +} + +#[test] +fn clone() { + let sv: StableVec = StableVec::new(); + assert_sv_eq!(sv.clone(), []: String); + + let sv = StableVec::from(&[2, 4]); + assert_sv_eq!(sv.clone(), [0 => 2, 1 => 4]); + + let mut sv = StableVec::from(&[2, 5, 4]); + sv.remove(1); + assert_sv_eq!(sv.clone(), [0 => 2, 2 => 4]); +} + +#[test] +fn iter_mut() { + let mut sv = StableVec::from(&[2, 5, 4]); + + for x in &mut sv { + *x *= 2; + } + assert_sv_eq!(sv, [0 => 4, 1 => 10, 2 => 8]); + + for x in sv.iter_mut() { + *x -= 1; } + assert_sv_eq!(sv, [0 => 3, 1 => 9, 2 => 7]); +} + +#[test] +fn index_mut() { + let mut sv = StableVec::from(&[2, 5, 4]); + + sv[1] = 8; + assert_sv_eq!(sv, [0 => 2, 1 => 8, 2 => 4]); + + sv[2] = 5; + assert_sv_eq!(sv, [0 => 2, 1 => 8, 2 => 5]); +} + +#[test] +fn index_panic() { + let mut sv = StableVec::from(&[2, 5, 4]); + sv.remove(1); + + assert_panic!(sv[1]); + assert_panic!(sv[3]); + + sv.reserve(10); + assert_panic!(sv[8]); +} + +#[test] +fn correct_drop() { + use std::sync::atomic::{Ordering, AtomicIsize}; + + static ALIVE_COUNT: AtomicIsize = AtomicIsize::new(0); + + struct Dummy(char); + impl Dummy { + fn new(c: char) -> Self { + ALIVE_COUNT.fetch_add(1, Ordering::SeqCst); + Self(c) + } + } + impl Drop for Dummy { + fn drop(&mut self) { + ALIVE_COUNT.fetch_sub(1, Ordering::SeqCst); + } + } + impl Clone for Dummy { + fn clone(&self) -> Self { + Self::new(self.0) + } + } + + let mut sv = StableVec::new(); + + sv.push(Dummy::new('a')); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 1); + + sv.push(Dummy::new('b')); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 2); + + sv.push(Dummy::new('c')); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 3); + + sv.remove(1); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 2); + + sv.extend_from_slice(&[Dummy::new('d'), Dummy::new('e')]); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 4); + + sv.pop(); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 3); + + sv.retain(|c| c.0 != 'd'); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 2); + + { + let mut clone = sv.clone(); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 4); + + clone.reordering_make_compact(); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 4); + } + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 2); + + + sv.make_compact(); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 2); + + sv.clear(); + assert_eq!(ALIVE_COUNT.load(Ordering::SeqCst), 0); } #[test] fn compact_tiny() { let mut sv = StableVec::from(&[1.0, 2.0, 3.0]); - assert!(sv.is_compact()); + assert_sv_eq!(sv, [0 => 1.0, 1 => 2.0, 2 => 3.0]); sv.remove(1); - assert!(!sv.is_compact()); - sv.make_compact(); assert_eq!(sv.into_vec(), &[1.0, 3.0]); } @@ -144,3 +534,72 @@ fn size_hints() { check_iter!(sv.iter_mut()); check_iter!(sv.keys()); } + +quickcheck! { + fn reordering_compact(insertions: u16, to_delete: Vec) -> bool { + let insertions = insertions + 1; + // Create stable vector containing `insertions` zeros. Afterwards, we + // remove at most half of those elements + let mut sv = StableVec::from(vec![0; insertions as usize]); + for i in to_delete { + let i = (i % insertions) as usize; + if sv.has_element_at(i) { + sv.remove(i); + } + } + + // Remember the number of elements before and call compact. + let sv_before = sv.clone(); + let n_before_compact = sv.num_elements(); + sv.reordering_make_compact(); + + n_before_compact == sv.num_elements() + && sv.is_compact() + && (0..n_before_compact).all(|i| sv.get(i).is_some()) + && sv_before.iter().all(|e| sv.contains(e)) + } + + fn compact(insertions: u16, to_delete: Vec) -> bool { + let insertions = insertions + 1; + // Create stable vector containing `insertions` zeros. Afterwards, we + // remove at most half of those elements + let mut sv = StableVec::from(vec![0; insertions as usize]); + for i in to_delete { + let i = (i % insertions) as usize; + if sv.has_element_at(i) { + sv.remove(i); + } + } + + // Remember the number of elements before and call compact. + let sv_before = sv.clone(); + let items_before: Vec<_> = sv_before.iter().cloned().collect(); + let n_before_compact = sv.num_elements(); + sv.make_compact(); + + + n_before_compact == sv.num_elements() + && sv.is_compact() + && (0..n_before_compact).all(|i| sv.get(i).is_some()) + && sv == items_before + } + + fn from_and_extend_and_from_iter(items: Vec) -> bool { + use std::iter::FromIterator; + + let iter_a = items.iter().cloned(); + let iter_b = items.iter().cloned(); + + let sv_a = StableVec::from_iter(iter_a); + let sv_b = { + let mut sv = StableVec::new(); + sv.extend(iter_b); + sv + }; + let sv_c = StableVec::from(&items); + + sv_a.num_elements() == items.len() + && sv_a == sv_b + && sv_a == sv_c + } +} From 261513ae9ae869d04b652d0dfba934d0cf1e47e7 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 24 Jan 2019 19:30:53 +0100 Subject: [PATCH 2/4] Fix memory safety bug by manually implementing `Clone` Before, I derived `Clone` all willy nilly. But that's very bad as the auto generated `clone()` can then access deleted elements. --- src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index dcb2f55..2a01fdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,7 +158,7 @@ mod tests; /// - [`shrink_to_fit()`](#method.shrink_to_fit) /// - [`reserve()`](#method.reserve) /// -#[derive(Clone, PartialEq, Eq)] +#[derive(PartialEq, Eq)] pub struct StableVec { /// Storing the actual data. data: Vec, @@ -901,6 +901,25 @@ impl StableVec { } } +impl Clone for StableVec { + fn clone(&self) -> Self { + let mut new_vec = Vec::with_capacity(self.data.len()); + + unsafe { + new_vec.set_len(self.data.len()); + for i in self.keys() { + ptr::write(new_vec.get_unchecked_mut(i), self[i].clone()); + } + } + + Self { + data: new_vec, + deleted: self.deleted.clone(), + used_count: self.used_count, + } + } +} + impl Drop for StableVec { fn drop(&mut self) { // We need to drop all elements that have not been removed. We can't From 442a57fffe9288cba75e0e334fd9381b517c0220 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 24 Jan 2019 19:31:32 +0100 Subject: [PATCH 3/4] Fix memory safety bug by correctly dropping elements in `clear()` Before, `Vec::clear()` was called, which dropped all elements, including deleted ones. --- src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2a01fdb..b225954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -675,7 +675,15 @@ impl StableVec { /// assert!(sv.capacity() >= 2); /// ``` pub fn clear(&mut self) { - self.data.clear(); + unsafe { + if mem::needs_drop::() { + for e in &mut *self { + ptr::drop_in_place(e); + } + } + self.data.set_len(0); + } + self.deleted.truncate(0); self.used_count = 0; } From 5863d4d058d8a8ac5e387c7252107b8e0b4e3ced Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 24 Jan 2019 19:46:33 +0100 Subject: [PATCH 4/4] Bump version to 0.3.1 and update CHANGELOG --- CHANGELOG.md | 9 +++++++++ Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e98c820..cfc4ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [0.3.1] - 2019-01-24 +### Fixed +- Fix memory safety bug in `clone()`: cloning a non-compact stable vec before + this change accessed already dropped values and attempted to clone them. +- Fix memory safety bug in `clear()`: calling `clear()` on a non-compact + stable vec before would access already dropped values and drop them a second + time. + ## [0.3.0] - 2019-01-07 ### Removed - Remove `IterMut::remove_current`. The method was broken because it did not @@ -78,6 +86,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [Unreleased]: https://github.com/LukasKalbertodt/stable-vec/compare/v0.3.0...HEAD +[0.3.1]: https://github.com/LukasKalbertodt/stable-vec/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/LukasKalbertodt/stable-vec/compare/v0.2.2...v0.3.0 [0.2.2]: https://github.com/LukasKalbertodt/stable-vec/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/LukasKalbertodt/stable-vec/compare/v0.2.0...v0.2.1 diff --git a/Cargo.toml b/Cargo.toml index 2c7d966..74a6494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stable-vec" -version = "0.3.0" +version = "0.3.1" authors = ["Lukas Kalbertodt "] description = """