I assume this is for learning purposes. I'll walk through how this can be done and some improvements that could be made.
Here's a first pass at modifying your program. I made minimal changes:
- Added the function you suggested
- Put most of your code in the function
- Renamed
arr
to numbers
in the function
- Added
large
at the end of the function to return it
- Left the array and
println
in main, and called the function to get large
There's still a warning about an unused initialization that we can get rid of:
- let mut large: i64 = 0;
let mut i: usize = 0;
-
- large = numbers[0];
+ let mut large = numbers[0];
But here's a question for you: What should happen when someone passes in an empty slice? Indexing element 0 will panic if the slice is empty in this version. We'll come back to do this later.
Next pass: It's much more idiomatic to use iterators in Rust than to do "manual" indexing like you're doing here. for
loops work with iterators, or more technically things that implement IntoIterator
(that can be turned into iterators). Shared slices like &[i64]
implement that trait, and the resulting iterator is over references to the contents (i.e. the iterator items are &i64
).
So we can rewrite your while
loop into a for
loop and get rid of i
:
for number in numbers {
if large < *number {
large = *number
}
}
At this point there's a number of minor tweaks that could be applied, for example to get rid of the need to dereference number
all the time. But now I want to return to the question about empty slices. We can address that by returning an Option
instead of an i64
, and then the caller can decide what they want to do (use a default value, panic, etc.)
fn max_number(numbers: &[i64]) -> Option<i64> {
let mut large = numbers.get(0)?;
for number in numbers {
if large < number {
large = number
}
}
Some(*large)
}
Here,
- I changed the signature to return
Option
-
get
is similar to indexing, but returns an Option
to a reference of the contents
- If the slice is empty,
.get(0)
will return None
instead of panicking
- The
?
will return from the function if get
returned None
; otherwise, it evaluates to the contained reference, which gets assigned to large
- Because
large
is a reference now, I removed all the dereferenes from number
- I wrapped
large
in Some
when returning it, and added a dereference to large
The biggest thing to learn here is probably the ?
operator. It works with Result
as well as Option
, and is covered in this part of the book.
We need one more change to make this compile:
fn main() {
let arr: [i64; 5] = [1, 2, 3, 4, 5];
- let large = max_number(&arr);
+ let large = max_number(&arr).unwrap();
println!("Largest element in the array: {}", large);
}
It's okay to unwrap
here -- we'll only get None
if the slice we pass in is empty, and we know that's not the case because we're passing in a reference to an array of size 5.
Here's the playground with those changes.
I think that's a good enough stopping point for this exercise.
Incidentally, finding the maximum element is a common enough operation that there's a generic way to do it in the standard library. It's a method on the Iterator
trait. So you could just do this:
// `large` is now a reference in this version...
let large = arr.iter().max().unwrap();
// ...or in this version it's still an `i64`
let large = arr.iter().copied().max().unwrap();
(But I've assumed that's not the point of this post.)
P.s. See the pinned post on code formatting for this forum.