Most efficient way to mutate u64 in functions?

My goal is to write a function to mutate several (2 to be specific) places of a Vec. However, this snippet seems to have no effect on the vector

fn main() {
    let mut v: Vec<u64> = vec![1,2,3]; 
    mutate_u64(v[0], v[1]); 
    println!("{:?}", v); 
}

fn mutate_u64(mut x: u64, mut y: u64) {
    x+=1; 
    y+=2;
}

I understand this might be because I'm not passing in a reference, so a copy is made inside the function. But I read somewhere that passing in reference is suboptimal in terms of performance.

My question then is why the compiler requires the "mut" key word in the function arguments? And what would be the recommended way for achieving the goal? Thanks!!

First, passing references is not suboptimal. It is the fastest you can get. And the compiler may inline it and remove it completely.

The compiler requires the mut keyword because you change the variable. What you want is this:

fn main() {
    let mut v: Vec<u64> = vec![1,2,3]; 
    mutate_u64(&mut v[0], &mut v[1]); 
    println!("{:?}", v); 
}

fn mutate_u64(x: &mut u64, y: &mut u64) {
    *x += 1; 
    *y += 2;
}

However, that doesn't pass borrow checking because you mutably-borrow v twice. There are multiple ways to work around that, but since you want the first elements the easiest way is a slice pattern:

fn main() {
    let mut v: Vec<u64> = vec![1,2,3]; 
    if let [x, y, ..] = v.as_mut_slice() { mutate_u64(x, y); }
    println!("{:?}", v); 
}

fn mutate_u64(x: &mut u64, y: &mut u64) {
    *x += 1; 
    *y += 2;
}
3 Likes
  1. mut keyword in fn mutate_u64(mut x: u64, mut y: u64) means that the varable x and y is mutable, you can change them in the function, if you remove the mut, you will not be able to change them. In this way you can avoid creating a "shadow" mutable variable in the fucntion.

  2. Primitive types (except u128 and i128) are less than 8 Bytes, and a reference is indeed a 64bit pointer (on x86-64 platforms), so passing a primative type should be as efficiency as passing a reference. However, if you pass a primitive by reference, you probobly will dereference it, so theoretically *x += 1 will be slower than x += 1.

So my suggestion is that if you are using primative types and don't need reference, pass by value.

P.S. In this case, references should be used since you want to modify elements in a vector, check other answers to deal with "double mutable reference" problem.

1 Like

Or go for this where you can throw in an arbitrary slice:

fn main() {
    let mut v: Vec<u64> = vec![1,2,3];
    mutate_u64(&mut v[0..=1]);
    println!("{:?}", v);
}

fn mutate_u64(v: &mut[u64]) {
    for u in v{
        *u += 1;
    }
}
1 Like

Perhaps I was misunderstood: I agree that they are less efficient than passing by-value. However, if the OP needs a to modify them, then references will be the fastest way available, and also very fast.

4 Likes

Thanks! I do need to mutate the other elements than the first few, it could be arbitrary two locations from the vector. Looking at the replies it seems mrQuidome's reply is closest to what I need. Really thanks for the help!

Oh thanks!! this should be exactly what I want. In terms of cost, I was wondering would it be faster to do this?

fn main(){
    let mut v: Vec<u64> = vec![1,2,3]; 
    let new = mutate_u64(v[0], v[1]); 
    v[0] = new.0; 
    v[1] = new.1; 
    println!("{:?}", v); 
}

fn mutate_u64(mut x: u64, mut y: u64) -> (u64, u64){
    x+=1; 
    y+=2;
    (x,y)
}

The compiler will almost certainly optimize them exactly the same. Do whatever is easier for you to read.

1 Like

I didn't make myself clear, I thought OP already know references should be used in this case. Edited my answer.

Modern compilers will use the tech Copy elision to optimize codes, return value is a typical scenario.

To check if the code is optimized as you want, you can use the playground, and choose "LLVM IR" beside the Run button (Or cargo rustc -- --emit=llvm-ir on your machine). Use this tool for checking whenever you are doubting about the optimization.

Thanks! Will try it out

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.