Functions
A Rust version of the famous FizzBuzz interview question:
fn main() {
print_fizzbuzz_to(20);
}
fn is_divisible(n: u32, divisor: u32) -> bool {
if divisor == 0 {
return false;
}
n % divisor == 0
}
fn fizzbuzz(n: u32) -> String {
let fizz = if is_divisible(n, 3) { "fizz" } else { "" };
let buzz = if is_divisible(n, 5) { "buzz" } else { "" };
if fizz.is_empty() && buzz.is_empty() {
return format!("{n}");
}
format!("{fizz}{buzz}")
}
fn print_fizzbuzz_to(n: u32) {
for i in 1..=n {
println!("{}", fizzbuzz(i));
}
}
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz
Notes:
- We refer in main to a function written below. Neither forward declarations nor headers are necessary.
- Declaration parameters are followed by a type (the reverse of some programming languages), then a return type.
- The last expression in a function body (or any block) becomes the return value. Simply omit the ; at the end of the expression.
- Some functions have no return value, and return the ‘unit type’, (). The compiler will infer this if the → () return type is omitted.
- The range expression in the for loop in print_fizzbuzz_to() contains =n, which causes it to include the upper bound.
Rustdoc
All language items in Rust can be documented using special / syntax.
/// Determine whether the first argument is divisible by the second argument.
///
/// If the second argument is zero, the result is false.
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
if rhs == 0 {
return false; // Corner case, early return
}
lhs % rhs == 0 // The last expression in a block is the return value
}
The contents are treated as Markdown. All published Rust library crates are automatically documented at docs.rs using the rustdoc tool. It is idiomatic to document all public items in an API using this pattern.
Notes
- Show students the generated docs for the rand crate at docs.rs/rand.
- Rustdoc comments can contain code snippets that we can run and test using cargo test in the Documentation Tests section.
Methods
Methods are functions associated with a type. The self argument of a method is an instance of the type it is associated with:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn inc_width(&mut self, delta: u32) {
self.width += delta;
}
}
fn main() {
let mut rect = Rectangle { width: 10, height: 5 };
println!("old area: {}", rect.area());
rect.inc_width(5);
println!("new area: {}", rect.area());
}
old area: 50
new area: 75
Notes
- Add a static method called Rectangle::new and call this from main:
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
- While technically, Rust does not have custom constructors, static methods are commonly used to initialize structs (but don’t have to). The actual constructor, Rectangle { width, height }, could be called directly. See the Rustnomicon.
- Add a Rectangle::square(width: u32) constructor to illustrate that such static methods can take arbitrary parameters.
Function Overloading
Overloading is not supported:
- Each function has a single implementation:
- Always takes a fixed number of parameters.
- Always takes a single set of parameter types.
- Default values are not supported:
- All call sites have the same number of arguments.
- Macros are sometimes used as an alternative.
However, function parameters can be generic:
fn pick_one<T>(a: T, b: T) -> T {
if std::process::id() % 2 == 0 { a } else { b }
}
fn main() {
println!("coin toss: {}", pick_one("heads", "tails"));
println!("cash prize: {}", pick_one(500, 1000));
}
coin toss: tails
cash prize: 1000