rustfizzbuzzlifetime-scoping

FizzBuzz Function in Rust


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.


Solution

  • 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));
        }
    }