Reputation: 1772
I am new to Rust and am trying to learn the idiomatic way to work with the borrow checker.
I'm trying to write a simple function that takes in a slice (generic over the data type) and returns the last element, in such a way that I can mutate the slice afterward. The naive implementation gives me an error:
fn last_element<T>(list: &[T]) -> T {
list[list.len() - 1]
}
fn main() {
let mut slice = [1, 2, 3, 4, 5];
let x = last_element(&slice);
println!("{}", x);
// I want to be able to mutate slice after extracting last element
slice[2] = 17;
}
The error is cannot move out of type
[T], a non-copy slice
. I see that one workaround is to have the function return a reference:
fn last_element<T>(list: &[T]) -> &T {
&list[list.len() - 1]
}
fn main() {
let mut slice = [1, 2, 3, 4, 5];
let x = last_element(&slice);
println!("{}", x);
// I want to be able to mutate slice after extracting last element
slice[2] = 17;
}
But then I get an error for slice[2] = 17
because the slice was borrowed when x
was assigned. I do want to be able to mutate after calling last_element
. The one workaround I found was to dereference x
, which I believe consumes the borrow:
fn last_element<T>(list: &[T]) -> &T {
&list[list.len() - 1]
}
fn main() {
let mut slice = [1, 2, 3, 4, 5];
let x = *last_element(&slice);
println!("{}", x);
// I want to be able to mutate slice after extracting last element
slice[2] = 17;
}
Is this the most idiomatic way to accomplish the aim of being able to get the last element of a slice and still mutate the slice afterward? Or should I never even be doing the mutation afterward if I am writing good code?
Upvotes: 1
Views: 449
Reputation: 58805
If you are working with simple types, such as i32
and other small, fixed-sized structures, these types usually implement Copy
. This trait is a marker which tells the compiler to copy values in memory in situations where they would otherwise be moved. In fact, this is what the error message means when it refers to a non-copy slice
: if the elements don't implement Copy
then it would have to move them and that would leave the slice in an invalid state, which is not allowed.
If you constrain your function to expect only Copy
types, then it will happily copy those values for you:
fn last_element<T: Copy>(list: &[T]) -> T {
list[list.len() - 1]
}
If the type of your elements could be more complex, and doesn't implement Copy
, then you can constrain the function wider, to any type that implements Clone
, and then call clone()
on the element before it is returned:
fn last_element<T: Clone>(list: &[T]) -> T {
list[list.len() - 1].clone()
}
Cloning is generally a heavier operation than copying, since it is usually implemented on per-field basis in Rust code, while Copy
is a lower-level operation on raw memory.
A final option would be to just return a reference to the last element in the first place. The caller of the function can then decide what to do with it. If it's a Copy
type (like numbers) then dereferencing it will copy it:
fn last_element<T>(list: &[T]) -> &T {
&list[list.len() - 1]
}
fn main() {
let mut slice = [1, 2, 3, 4, 5];
let x = *last_element(&slice);
}
If the elements were Clone
but not Copy
then, instead of dereferencing, you can explicitly clone
it at that point instead:
let x = last_element(&slice).clone();
Upvotes: 5