1

I am working on implementing a sieve of atkins as my first decently sized program in rust. This algorithm takes a number and returns a vector of all primes below that number. There are two different vectors I need use this function.

  1. BitVec 1 for prime 0 for not prime (flipped back and forth as part of the algorithm).
  2. Vector containing all known primes.

The size of the BitVec is known as soon as the function is called. While the final size of the vector containing all known primes is not known, there are relatively accurate upper limits for the number of primes in a range. Using these I can set the size of the vector to an upper bound then shrink_to_fit it before returning. The upshot of this neither array should ever need to have it's capacity increased while the algorithm is running, and if this happens something has gone horribly wrong with the algorithm.

Therefore, I would like my function to panic if the capacity of either the vector or the bitvec is changed during the running of the function. Is this possible and if so how would I be best off implementing it?

Thanks,

Pioneer_11
  • 670
  • 4
  • 19
  • Are you asking how to check if the capacity has changed? Would the [capacity()](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.capacity) method fulfill your need? Or are you wanting your code to panic immediately after a push() if the push resulted in the capacity increasing? – effect Nov 18 '22 at 00:46
  • @effect my assumption is that as rusts's vec automatically increases capacity if something is pushed to it when len == capacity, rust must already make the required check. Ideally what I am looking for is something to replace "if push and len equals capacity increase capacity" to "if push and len equals capacity panic" – Pioneer_11 Nov 18 '22 at 01:21
  • 1
    "rust must already make the required check" well yes but it's an intrinsic part of the entire point and expected behaviour of a vec. Though you could probably use a custom allocator which can be "locked": on resize, the Vec will request a resize from the allocator, the allocator can reject the allocation then. – Masklinn Nov 18 '22 at 06:44

2 Answers2

2

You can assert that the vecs capacity() and len() are different before each push:

assert_ne!(v.capacity(), v.len());
v.push(value);

If you want it done automatically you'd have to wrap your vec in a newtype:

struct FixedSizeVec<T>(Vec<T>);
impl<T> FixedSizeVec<T> {
    pub fn push(&mut self, value: T) {
        assert_ne!(self.0.len(), self.0.capacity())
        self.0.push(value)
    }
}

To save on forwarding unchanged methods you can impl Deref(Mut) for your newtype.

use std::ops::{Deref, DerefMut};
impl<T> Deref for FixedSizeVec<T> {
    type Target = Vec<T>;
    fn deref(&self) -> &Vec<T> {
        &self.0
    }
}
impl<T> DerefMut for FixedSizeVec<T> {
    fn deref_mut(&mut self) -> &mut Vec<T> {
        &mut self.0
    }
}
cafce25
  • 15,907
  • 4
  • 25
  • 31
2

An alternative to the newtype pattern is to create a new trait with a method that performs the check, and implement it for the vector like so:

trait PushCheck<T> {
    fn push_with_check(&mut self, value: T);
}

impl<T> PushCheck<T> for std::vec::Vec<T> {
    fn push_with_check(&mut self, value: T) {
        let prev_capacity = self.capacity();
        self.push(value);
        assert!(prev_capacity == self.capacity());
    }
}

fn main() {
    let mut v = Vec::new();
    v.reserve(4);
    dbg!(v.capacity());
    
    v.push_with_check(1);
    v.push_with_check(1);
    v.push_with_check(1);
    v.push_with_check(1);
    
    // This push will panic
    v.push_with_check(1);
}

The upside is that you aren't creating a new type, but the obvious downside is you need to remember to use the newly defined method.

effect
  • 1,279
  • 6
  • 13