Borrowing question

apologies for posting this question twice, I'm on a windows machine at work and not familiar with the keyboard shortcuts. This is my first post and I've just started learning rust which is really exciting, I dont come from a computer science background and only ever written interpreted languages. I find the rust documentation and resources incredible and it makes learning the fundamentals fun :slightly_smiling_face: I'm trying to get my head around ownership, I have the simple code snippet below. I can understand why a reference is used when we pass the vector into the count function so we don't transfer ownership of the vector. I assume we don't need to pass a reference to i in count because its an scalar type that's stored in the stack and copied into the function. I'd be grateful if someone can explain why we're using references &i and &v in the for loop though?

fn main() {
	let v = vec![1, 3, 5, 5, 6, 7, 4, 5, 2, 4, 5, 1, 5];

	for &i in &v {
		let r = count(&v, i);
		println!("{} is repeated {} times", i, r);
	}
}

fn count(v: &Vec<i32>, val: i32) -> usize {
	v.into_iter().filter(|&&x| x == val).count()
}

The variable v is immutable, when you iterate over something using the for loop syntax it will call into_iter on it, taking self as argument and receive ownership of it.
But you use v again when you call count so you can't loose ownership. This explains the & in front of v, this time it will take a &Vec<i32> (&[i32] to be exact) and you can still give immutable references to whatever you want.
Since you iterate over &[i32] i will have the type &i32 but you said count takes a i32, by putting a & in front of i you use pattern matching to remove the reference and copy the i32.

1 Like

@leudz very nice explanation, thank you :slight_smile:

I tweaked the code snippet based on your feedback. I find this explicit referencing and dereferencing a bit easier to reason about

fn main() {
	let v = vec![1, 3, 5, 5, 6, 7, 4, 5, 2, 4, 5, 1, 5];

	for &i in &v {
		let r = count(&v, &i);
		println!("{} is repeated {} times", i, r);
	}
}

fn count(v: &Vec<i32>, val: &i32) -> usize {
	v.into_iter().filter(|&&x| x == *val).count()
}

If count takes a &i32 then you don't need to remove the reference and add it again on i. And same thing with x and val, you can remove a & and *.

for i in &v {
    let r = count(&v, i);
    println!("{} is repeated {} times", i, r);
}

v.into_iter().filter(|&x| x == val).count()

But in general it's a good idea to just pass small plain data by value and especially with Copy types such as i32. You will borrow less and have fewer problems with the borrow checker. Also on 64 bits architectures if the compiler isn't smart enough you are making a 64 bits pointer to a 32 bits value, that feels weird.

1 Like

thanks again @leudz very insightful :slightly_smiling_face:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.