FizzBuzz Function in Rust

430 Views Asked by At

This is my code:

fn main() { 
  fn fizz_buzz<'a>(i: i32) -> &'a str { 

    if i % 15 == 0 { 
        "FizzBuzz"
    } else if i % 5 == 0 { 
        "Buzz"
    } else if i % 3 == 0 { 
        "Fizz"
    } else { 
        &i.to_string()
    }
  }

  for i in 1..101 { 
    println!("{}" , fizz_buzz(i));
  }
}

The compiler gives me this error:

error[E0515]: cannot return reference to temporary value
  --> src/main.rs:11:9
   |
11 |         &i.to_string()
   |         ^-------------
   |         ||
   |         |temporary value created here
   |         returns a reference to data owned by the current function

For more information about this error, try `rustc --explain E0515`.
error: could not compile `playground` due to previous error

I tried a static lifetime.

2

There are 2 best solutions below

4
Adam Smith On BEST ANSWER

Your function will correctly give back a reference to the strings "FizzBuzz," "Buzz," and "Fizz" (whose lifetimes are static since they're compiled in) however the &i.to_string() does not have that same property. Let's look at the lifetime in detail:

When fizz_buzz is called, i is copied (because i32 implements the Copy trait) and given to it. In that else block, however, we do the following:

  1. Create a new owned String
  2. Return a reference to that String

however, the lifetime of that String is only as long as the fizz_buzz function call! Since we need to use its reference outside of that scope, Rust calls foul.

There are a couple ways to make this type safe. You could return owned values rather than references:

fn fizz_buzz(i: i32) -> String {
    if i % 15 == 0 { String::from("FizzBuzz") }
    else if i % 5 == 0 { String::from("Buzz") }
    else if i % 3 == 0 { String::from("Fizz") }
    else { i.to_string() }
}

Though this will end up creating a lot of identical objects on the heap (consider how many "Fizz"es there are, for instance)

The other option that I'd prefer is to have fizz_buzz return an Option<&str>, and have the calling scope handle the case when fizz_buzz gives None.

fn fizz_buzz(i: i32) -> Option<&'static str> {
    if i % 15 == 0 { Some("FizzBuzz") }
    else if i % 5 == 0 { Some("Buzz") }
    else if i % 3 == 0 { Some("Fizz") }
    else { None }
}

for i in 1..101 {
    match fizz_buzz(i) {
        Some(v) => println!("{}", v),
        None => println!("{}", i),
    }
}

As @RobinZigmond points out in the comments, you could also return an enum and implement Display for it.

use std::fmt::{self, Display};

enum FizzBuzz {
    FizzBuzz,
    Fizz,
    Buzz,
    Other(i32)
}

impl Display for FizzBuzz {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FizzBuzz => write!(f, "{}", "FizzBuzz"),
            Self::Fizz => write!(f, "{}", "Fizz"),
            Self::Buzz => write!(f, "{}", "Buzz"),
            Self::Other(i) => write!(f, "{}", i.to_string())
        }
    }
}

fn fizz_buzz(i: i32) -> FizzBuzz {
    if i % 15 == 0 { FizzBuzz::FizzBuzz }
    else if i % 5 == 0 { FizzBuzz::Buzz }
    else if i % 3 == 0 { FizzBuzz::Fizz }
    else { FizzBuzz::Other(i) }
}

fn main() {
    for i in 1..101 {
        println!("{}", fizz_buzz(i));
    }
}
1
Nathaniel Ford On

You are trying to return a reference to a value that only exists on the stack frame, a frame that goes away when you exit the function. This fixes your problem:

fn main() {
    fn fizz_buzz(i: i32) -> String {

        if i % 15 == 0 {
            "FizzBuzz".to_string()
        } else if i % 5 == 0 {
            "Buzz".to_string()
        } else if i % 3 == 0 {
            "Fizz".to_string()
        } else {
            i.to_string()
        }
    }

    for i in 1..101 {
        println!("{}" , fizz_buzz(i));
    }
}

The key learning is that a String is a string that exists on the heap, and not the stack. The only way to create a dynamic string (such as you do when you cast the i32 to a string) is to put it on the stack. Thus, you have to change your method signature.

(I like Adam Smith's answer better, though! It makes it clear how Rust's notion of lifetimes affects this.)