A Friendly Challenge for Rust


#1

Hello Rusteans,

In Oct/Nov 2018 I released some code to do my fast Twin Prime Segmented Sieve of Zakiya (SSoZ).

The original version was done in Nim.

I have just finished a paper explaining the algorithm and code, as well as the math, et al.

Here’s the Nim source code shown in the paper. You can compile it as is.

Here are two links to the paper.

I’ve been following Rust for awhile, hear real (mostly) good things about it, but it would require me
to put in a lot of time to become proficient at it (I’ve tried), and haven’t really had the necessary time.

My friendly challenge then is for someone(s) to do a Rust version of the algorithm.

Here are some of the specifics that are required.

  1. It has to be a true parallel implementation using the max number of cpu threads.

  2. Nim has a garbage collector, that recovers mem after each thread finishes. I don’t know what
    Rust would have to do with this regard.

  3. The code has specific compile time actions (computing a lot constant values).

  4. I use a rudimentary segmentation strategy in the Nim code, which surely can be improved.

  5. The segment arrays are byte arrays, but maybe fast bitarrays could potentially be faster.

The paper shows it performs better compared to a C++ project (primesieve.org), as a reference.

I am REALLY interested to see how a well implemented Rust version compares (extra points for a GPU version).

Thanks.

Jabari


#2

I think this will be helpful for me to learn how to use Nim. The documentation for Nim is rather scarce / non-existent. I’ve been struggling to figure out the basics. Something you can say about Rust is that there’s significant documentation and resources for almost every use case today.


#3

Is this the same algorithm you asked about before?


#4

The mathematical basis is the same, but I’ve refined and re-architectured its implementation, and customized it to perform an SSoZ for Twin Primes (which can be modified to find any k-tuples primes).

In the first paper I used C++, which really didn’t work as intended (true parallel operation). The current Nim version code design components are generic, and came be architectured for a general total primes sieve, sieving over ranges, etc. And it’s much simpler|shorter than before. (There’s a 4 year difference between these papers).

No one really presented a Rust version from the first request. I took 6 months to write the current paper, in great detail with visual aides, to explain the math and process as much as reasonably necessary to understand the math and code.

It took a while to beat on Nim to get it to completely do what I wanted (it has an extreme lack of good documentation, examples, tutorials, and videos). And Rust is just too different to learn, and would require much more time for me to become functional enough to produce comparable code.

But if the Nim code can produce it’s (very fast) results, can a Rust version be faster?


#5

You can always request for a tutor. Many of us are around to show you how to do anything you might want to do. I’ll take a quick look at translation some of this Nim code tonight.

Most people use rayon, which lets you convert any Iterator type into a parallel iterator that sends each task to a thread pool in the background, and may also collect the results back.

let hundred_squares = (0..100)
    .par_bridge()
    .map(|x| x * x)
    .collect::<Vec<u32>>();

Nothing needs to be done because memory is reclaimed by the kernel when a thread exits. In day to day scenarios, memory is automatically reclaimed by the type system at the end of the lifetime of the value that holds allocated memory.


#6

Here’s a conversion of the modinv function:

fn modinv(lhs0: u32, rhs0: u32) -> u32 {
    let mut result = 1;

    if rhs0 == 1 { return result }
    
    let (mut lhs, mut rhs, mut x0) = (lhs0, rhs0, 0);

    while lhs > 1 {
        let q = lhs / rhs;
        lhs = lhs % rhs;
        
        let mut temporary = rhs;
        lhs = rhs;
        rhs = temporary;

        result -= q * x0;

        temporary = x0;
        x0 = result;
        result = x0;
    }
    
    if result < 0 {
        result + rhs0
    } else {
        result
    }
}

#7
if gcd(modpg, pc) == 1: residues.add(pc); residues.add(modpg - pc)

Does this mean the following?

if modpg.gcd(&pc) == 1 {
    residues.push(pc);
    residues.push(modpg-pc);
}

Or this?

if modpg.gcd(&pc) == 1 {
    residues.push(pc);
}

residues.push(modpg-pc);

Space-sensitive language syntax is dangerous to the reader.


#8

I think that result = x0; was intended to be result = temporary;?
mem::swap exists so that the loop can be shortened to:

    while lhs > 1 {
        result -= (lhs / rhs) * x0;
        lhs = lhs % rhs;
        std::mem::swap(&mut lhs, &mut rhs);
        std::mem::swap(&mut x0, &mut result);
    }

Honestly, it would be nice if rust allowed us to write

        (lhs, rhs) = (rhs, lhs % rhs);

without a let binding to collapse the middle two lines for clarity. I noticed on rosetta code that the rust solution uses a tuple variable for this purpose for brevity, but it’s less readable than lhs & rhs.


#9

It’s the first case.

Nim uses Python like space indentation, so those two statements are part of the if expression.
So when you see one-liners like that in the code it’s all under one expression. Just my style.


#10

I had to change u32 to i32 to get it to work.

fn modinv(lhs0: i32, rhs0: i32) -> i32 {
    let mut result = 1;

    if rhs0 == 1 { return result }
    
    let (mut lhs, mut rhs, mut x0) = (lhs0, rhs0, 0);

    while lhs > 1 {
        result -= (lhs / rhs) * x0;
        lhs = lhs % rhs;
        std::mem::swap(&mut lhs, &mut rhs);
        std::mem::swap(&mut x0, &mut result);
    }
    
    if result < 0 {
        result + rhs0
    } else {
        result
    }
}

fn main() {
    println!("{}", modinv( 7, 30));  // => 13
    println!("{}", modinv(11, 30));  // => 11
    println!("{}", modinv(13, 30));  // =>  7
    println!("{}", modinv(17, 30));  // => 23
    println!("{}", modinv(19, 30));  // => 19
    println!("{}", modinv(23, 30));  // => 17
    println!("{}", modinv(29, 30));  // => 29
    println!("{}", modinv(31, 30));  // =>  1 -> 31
}

#11

A little shorter.

fn modinv(lhs0: i32, rhs0: i32) -> i32 {
    let mut result = 1;

    if rhs0 == 1 { return result }
    
    let (mut lhs, mut rhs, mut x0) = (lhs0, rhs0, 0);

    while lhs > 1 {
        result -= (lhs / rhs) * x0;
        lhs = lhs % rhs;
        std::mem::swap(&mut lhs, &mut rhs);
        std::mem::swap(&mut x0, &mut result);
    }

    if result < 0 { result += rhs0 }
    result
}

#12

This is my Rust translation of the Nim sozpg function, but I’m messing up somehow declaring the global variables.

What am I doing wrong in the code?

use std::mem;

// Global variables
let modpg = 30;
let rescnt = 8;
let residues = [7, 11,13, 17, 19, 23, 29, 31];
let mut pcnt = 0;
let mut primes = vec![i32];

fn sozpg(val: isize) {
  // Compute the list of primes r1..sqrt(input_num), and store in global
  // 'primes' array, and store their count in global var 'pcnt'.
  // Any algorithm (fast|small) is usable. Here the SoZ for the PG is used.
  let md = &modpg;                  // PG's modulus value
  let rscnt = &rescnt;              // PG's residue count
  let res = &residues;              // PG's residues list

  let num = (val - 1) | 1;          // if val even then subtract 1
  let mut k = num / md;             // compute its residue group value
  let mut modk = md * k;            // compute the resgroup base value
  let mut r = 0;                    // from 1st residue in num's resgroup
  while num >= modk + res[r] { r += 1 }  // find last pc val|position <= num
  let maxpcs = k * rscnt + r;       // max number of prime candidates <= num
  let mut prms: [bool; maxpcs] = [false; maxpcs]; // array of prime candidates set False

  let sqrtN = (num as f64).sqrt() as i32  // compute integer sqrt of input num
  modk = 0; r = -1; k = 0;          // initialize sieve parameters

  // mark the multiples of the primes r1..sqrtN in 'prms'
  for prm in prms.iter() {          // from list of pc positions in prms
    r += 1; if r == rscnt { r = 0; modk += md; k += 1 }
    if prm { continue }             // if pc not prime go to next location
    let prm_r = res[r];             // if prime save its residue value
    let prime = modk + prm_r;       // numerate the prime value
    if  prime > sqrtN { break }     // we're finished when it's > sqrtN
    let prmstep = prime * rscnt;    // prime's stepsize to mark its mults
    for ri in res.iter {            // mark prime's multiples in prms
      let prod = prm_r * ri - 2;    // compute cross-product for r|ri pair
      // compute resgroup val of 1st prime multiple for each restrack
      // starting there, mark all prime multiples on restrack upto end of prms
      var mult prm_mult = (k * (prime + ri) + prod/md) * rscnt + pos[prod % md];
      while prm_mult < maxpcs { prms[prm_mult] = true; prm_mult += prmstep }
    }
  }
  // prms now contains the nonprime positions for the prime candidates r1..N
  // extract primes into global var 'primes' and count into global var 'pcnt'
  primes = Vec::new();              // create empty dynamic array for primes
  modk = 0; r = -1;                 // initialize loop parameters
  for prm in prms.iter() {          // numerate|store primes from pcs list
    r += 1; if r == rscnt { r = 0; modk += md }
    if !prm { primes.push(modk + res[r]) } // put prime in global 'primes' list
  }
  pcnt = primes.len();              // set global count of primes
}

fn main() {
  sozpg(1_000);
  println!("{}", primes);
  println!("{}", pcnt);
}

I keep getting this error message when I compile it, using 1.31.1.

➜  rustc sozpg.rs
error: expected item, found `let`
 --> sozpg.rs:4:1
  |
4 | let modpg = 30;
  | ^^^ expected item

error: aborting due to previous error


#13

You can’t declare a variable with let in the global scope. You can create a static variable, but then you must annotate the type:

static MOD_PG: i32 = 30;

Another restriction is that static variables can’t allocate memory on the heap.


#14

I don’t understand these error messages explanations.

I compute a value maxpcs, which is the array|vector size of a mutable boolean array|vector prms. Says maxpcs needs to be a constant, but it’s dterm at runtime.

Then it won’t let me read an element from an array, but array index r is an i32.

error[E0435]: attempt to use a non-constant value in a constant
  --> sozpg.rs:26:37
   |
26 |   let mut prms: Vec<bool> = [false; maxpcs]; // array of prime candidates set False
   |                                     ^^^^^^ non-constant value

error[E0277]: the trait bound `i32: std::slice::SliceIndex<[i32]>` is not satisfied
  --> sozpg.rs:24:23
   |
24 |   while num >= modk + res[r] { r += 1 }  // find last pc val|position <= num
   |                       ^^^^^^ slice indices are of type `usize` or ranges of `usize`
   |
   = help: the trait `std::slice::SliceIndex<[i32]>` is not implemented for `i32`
   = note: required because of the requirements on the impl of `std::ops::Index<i32>` for `[i32]`error[E0435]: attempt to use a non-constant value in a constant
  --> sozpg.rs:26:37
   |
26 |   let mut prms: Vec<bool> = [false; maxpcs]; // array of prime candidates set False
   |                                     ^^^^^^ non-constant value

error[E0277]: the trait bound `i32: std::slice::SliceIndex<[i32]>` is not satisfied
  --> sozpg.rs:24:23
   |
24 |   while num >= modk + res[r] { r += 1 }  // find last pc val|position <= num
   |                       ^^^^^^ slice indices are of type `usize` or ranges of `usize`
   |
   = help: the trait `std::slice::SliceIndex<[i32]>` is not implemented for `i32`
   = note: required because of the requirements on the impl of `std::ops::Index<i32>` for `[i32]`

#15

I think Rust works just like C++ when allocating memory: you can’t use a variable without allocating dynamically. What you can do is
let mut prms: Vec<bool> = Vec::with_capacity(maxpcs);

Rust requires all variables used for indexing to be of type usize. A safe fix would be to change the type of the variable when declaring it, but res[r as usize] should work too.


#16

Thanks! Here’s the code now. The remaining problems are:

  1. I can’t read and write into prms, which causes problems in multiple places.
  2. I want to create global mutable vector primes, but having problems.

Here’s all the code now.

// Global variables
static modpg: i32 = 30;
static rescnt: i32 = 8;
static residues: [i32; 8] = [7, 11,13, 17, 19, 23, 29, 31];
static pos: [i32; 30] = [0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,3,0,4,0,0,0,5,0,0,0,0,0,6,0,7];
static mut pcnt: u32 = 0;
//static mut primes: Vec<i32> = vec![];

fn sozpg(val: i32) {
  // Compute the list of primes r1..sqrt(input_num), and store in global
  // 'primes' array, and store their count in global var 'pcnt'.
  // Any algorithm (fast|small) is usable. Here the SoZ for the PG is used.
  let md = modpg;                  // PG's modulus value
  let rscnt = rescnt;              // PG's residue count
  let res = residues;              // PG's residues list

  let num = (val - 1) | 1;          // if val even then subtract 1
  let mut k = num / md;             // compute its residue group value
  let mut modk = md * k;            // compute the resgroup base value
  let mut r = 0i32;                 // from 1st residue in num's resgroup
  while num >= modk + res[r as usize] { r += 1 }  // find last pc val|position <= num
  let maxpcs = k * rscnt + r;       // max number of prime candidates <= num
  let mut prms: Vec<bool> = Vec::with_capacity(maxpcs as usize); // array of prime candidates set False

  let sqrtN = (num as f64).sqrt() as i32; // compute integer sqrt of input num
  modk = 0; r = -1; k = 0;          // initialize sieve parameters

  // mark the multiples of the primes r1..sqrtN in 'prms'
  for &prm in prms.iter() {         // from list of pc positions in prms
    r += 1; if r == rscnt { r = 0; modk += md; k += 1 }
    if prm { continue }             // if pc not prime go to next location
    let prm_r = res[r as usize];    // if prime save its residue value
    let prime = modk + prm_r;       // numerate the prime value
    if  prime > sqrtN { break }     // we're finished when it's > sqrtN
    let prmstep = prime * rscnt;    // prime's stepsize to mark its mults
    for ri in res.iter() {          // mark prime's multiples in prms
      let prod = prm_r * ri - 2;    // compute cross-product for r|ri pair
      // compute resgroup val of 1st prime multiple for each restrack
      // starting there, mark all prime multiples on restrack upto end of prms
      let mut prm_mult = (k * (prime + ri) + prod/md) * rscnt + pos[(prod % md) as usize];
      while prm_mult < maxpcs { prms[prm_mult as usize] = true; prm_mult += prmstep }
    }
  }
  // prms now contains the nonprime positions for the prime candidates r1..N
  // extract primes into global var 'primes' and count into global var 'pcnt'
  let mut primes = Vec::new();      // create empty dynamic array for primes
  modk = 0; r = -1;                 // initialize loop parameters
  for &prm in prms.iter() {          // numerate|store primes from pcs list
    r += 1; if r == rscnt { r = 0; modk += md }
    if !prm { primes.push(modk + res[r as usize]) }// put prime in global 'primes' list
  }
  pcnt = primes.len() as u32;       // set global count of primes
}

fn main() {
  sozpg(1_000);
  //println!("{}", primes);
  println!("{}", pcnt);
}

Here are the remaining error messages.

error[E0502]: cannot borrow `prms` as mutable because it is also borrowed as immutable
  --> sozpg.rs:41:33
   |
29 |   for &prm in prms.iter() {         // from list of pc positions in prms
   |               ----      - immutable borrow ends here
   |               |
   |               immutable borrow occurs here
...
41 |       while prm_mult < maxpcs { prms[prm_mult as usize] = true; prm_mult += prmstep }
   |                                 ^^^^ mutable borrow occurs here

error[E0133]: use of mutable static is unsafe and requires unsafe function or block
  --> sozpg.rs:52:3
   |
52 |   pcnt = primes.len() as u32;       // set global count of primes
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^ use of mutable static
   |
   = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

error[E0133]: use of mutable static is unsafe and requires unsafe function or block
  --> sozpg.rs:58:18
   |
58 |   println!("{}", pcnt);
   |                  ^^^^ use of mutable static
   |
   = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

error: aborting due to 3 previous errors

I’m getting the distinct feeling there is no direct translation for Nim to Rust, as Rust has so many more requirements to adhere to to write equivalent performing code.

Also, if there a standard way to write the code without all the explicit xx as yyy?


#17

I am not aware of any. Once you get accustomed with the types you will need for different operations you will be able to choose a type which requires a minimal number of type casts.

Use for i in 0usize..maxpcs to iterate over the indices, this will solve the problem.

Mutable statics are unsafe : https://doc.rust-lang.org/nomicon/README.html
I’m also trying to write an implementation in Rust at the moment. The way I chose to overcome the problem of global variables was by creating a struct with all of them and writing the functions as specifics for an object of that structure

Rust concurrency is less trivial than Nim’s: https://doc.rust-lang.org/book/ch16-00-concurrency.html

It’s not possible to do something like this in Rust. If you want a vector, you have to pick something fixed-size like [i32, 100]. The way I ‘precompiled’ parametersp_ was by printing the result in a file and pasting it in a separate source file.


#18

Thanks again for all the help, from everyone.
It now compiles, and gives correct answers.
Here’s the final code.

// Global variables
static modpg: i32 = 30;
static rescnt: i32 = 8;
static residues: [i32; 8] = [7, 11,13, 17, 19, 23, 29, 31];
static pos: [i32; 30] = [0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,3,0,4,0,0,0,5,0,0,0,0,0,6,0,7];
static mut pcnt: u32 = 0;

fn sozpg(val: i32) {
  // Compute the list of primes r1..sqrt(input_num), and store in global
  // 'primes' array, and store their count in global var 'pcnt'.
  // Any algorithm (fast|small) is usable. Here the SoZ for the PG is used.
  let md = modpg;                  // PG's modulus value
  let rscnt = rescnt;              // PG's residue count
  let res = residues;              // PG's residues list

  let num = (val - 1) | 1;          // if val even then subtract 1
  let mut k = num / md;             // compute its residue group value
  let mut modk = md * k;            // compute the resgroup base value
  let mut r = 0i32;                 // from 1st residue in num's resgroup
  while num >= modk + res[r as usize] { r += 1 }  // find last pc val|position <= num
  let maxpcs = k * rscnt + r;       // max number of prime candidates <= num
  let mut prms: Vec<bool> = Vec::with_capacity(maxpcs as usize); // pcs array, set False
  prms = vec![false; maxpcs as usize];
  let sqrtN = (num as f64).sqrt() as i32; // compute integer sqrt of input num
  modk = 0; r = -1; k = 0;          // initialize sieve parameters

  // mark the multiples of the primes r1..sqrtN in 'prms'
  for i in 0..maxpcs {              // from list of pc positions in prms
    r += 1; if r == rscnt { r = 0; modk += md; k += 1 }
    if prms[i as usize] {continue}  // if pc not prime go to next location
    let prm_r = res[r as usize];    // if prime save its residue value
    let prime = modk + prm_r;       // numerate the prime value
    if  prime > sqrtN { break }     // we're finished when it's > sqrtN
    let prmstep = prime * rscnt;    // prime's stepsize to mark its mults
    for ri in res.iter() {          // mark prime's multiples in prms
      let prod = prm_r * ri - 2;    // compute cross-product for r|ri pair
      // compute resgroup val of 1st prime multiple for each restrack
      // starting there, mark all prime multiples on restrack upto end of prms
      let mut prm_mult = (k * (prime + ri) + prod/md) * rscnt + pos[(prod % md) as usize];
      while prm_mult < maxpcs { prms[prm_mult as usize] = true; prm_mult += prmstep }
    }
  }
  // prms now contains the nonprime positions for the prime candidates r1..N
  // extract primes into global var 'primes' and count into global var 'pcnt'
  let mut primes = Vec::new();      // create empty dynamic array for primes
  modk = 0; r = -1;                 // initialize loop parameters
  for &prm in prms.iter() {          // numerate|store primes from pcs list
    r += 1; if r == rscnt { r = 0; modk += md }
    if !prm { primes.push(modk + res[r as usize]) }// put prime in global 'primes' list
  }
  unsafe { pcnt = primes.len() as u32 };       // set global count of primes
}

fn main() {
  sozpg(2_000_000_000);
  //println!("{}", primes);
  println!("{}", unsafe {pcnt});
}

To get it to work I had to use unsafe around pcnt, and do the following to fully initialize prms. Is there a way to do it in one step?

  let mut prms: Vec<bool> = Vec::with_capacity(maxpcs as usize);
  prms = vec![false; maxpcs as usize];

To get around having primes as a global vector, I’ll just return it as the output of sozpg in my main, and pass it to the other routines that use it. This will also allow me to eliminate pcnt too, as it’s derived from primes.

I’d be interested to hear how to write this more Rust idiomatic.


#19

Here’s the code without global pcnt and primes.

// Global variables
static modpg: i32 = 30;
static rescnt: i32 = 8;
static residues: [i32; 8] = [7, 11,13, 17, 19, 23, 29, 31];
static pos: [i32; 30] = [0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,3,0,4,0,0,0,5,0,0,0,0,0,6,0,7];

fn sozpg(val: i32) -> Vec<i32> {
  // Compute the list of primes r1..sqrt(input_num), and store in global
  // 'primes' array, and store their count in global var 'pcnt'.
  // Any algorithm (fast|small) is usable. Here the SoZ for the PG is used.
  let md = modpg;                  // PG's modulus value
  let rscnt = rescnt;              // PG's residue count
  let res = residues;              // PG's residues list

  let num = (val - 1) | 1;          // if val even then subtract 1
  let mut k = num / md;             // compute its residue group value
  let mut modk = md * k;            // compute the resgroup base value
  let mut r = 0i32;                 // from 1st residue in num's resgroup
  while num >= modk + res[r as usize] { r += 1 }  // find last pc val|position <= num
  let maxpcs = k * rscnt + r;       // max number of prime candidates <= num
  let mut prms: Vec<bool> = Vec::with_capacity(maxpcs as usize); // pcs array, set False
  prms = vec![false; maxpcs as usize];
  let sqrtN = (num as f64).sqrt() as i32; // compute integer sqrt of input num
  modk = 0; r = -1; k = 0;          // initialize sieve parameters

  // mark the multiples of the primes r1..sqrtN in 'prms'
  for i in 0..maxpcs {              // from list of pc positions in prms
    r += 1; if r == rscnt { r = 0; modk += md; k += 1 }
    if prms[i as usize] { continue }// if pc not prime go to next location
    let prm_r = res[r as usize];    // if prime save its residue value
    let prime = modk + prm_r;       // numerate the prime value
    if  prime > sqrtN { break }     // we're finished when it's > sqrtN
    let prmstep = prime * rscnt;    // prime's stepsize to mark its mults
    for ri in res.iter() {          // mark prime's multiples in prms
      let prod = prm_r * ri - 2;    // compute cross-product for r|ri pair
      // compute resgroup val of 1st prime multiple for each restrack
      // starting there, mark all prime multiples on restrack upto end of prms
      let mut prm_mult = (k * (prime + ri) + prod/md) * rscnt + pos[(prod % md) as usize];
      while prm_mult < maxpcs { prms[prm_mult as usize] = true; prm_mult += prmstep }
    }
  }
  // prms now contains the nonprime positions for the prime candidates r1..N
  // extract primes into global var 'primes' and count into global var 'pcnt'
  let mut primes = Vec::new();      // create empty dynamic array for primes
  modk = 0; r = -1;                 // initialize loop parameters
  for &prm in prms.iter() {          // numerate|store primes from pcs list
    r += 1; if r == rscnt { r = 0; modk += md }
    if !prm { primes.push(modk + res[r as usize]) }// put prime in global 'primes' list
  }
  primes
}

fn main() {
  println!("{:?}", sozpg(541) );
  println!("{}", sozpg(541).len() );
}

And output.

➜  time ./sozpg  
[7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
97
./sozpg  0.00s user 0.00s system 82% cpu 0.001 total

#20

How do you create an array or vector, at runtime, with a specific size?

I compute maxpcs at runtime, but an array needs a numerical constant as it size, and I don’t see how to use maxpcs to create a vector of its size. Is this possilbe?