-2

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.

Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
dpokey
  • 64
  • 6

2 Answers2

5

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));
    }
}
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • 2
    Taking your second example further, I'd personally prefer returning values in an enum: `enum FizzBuzzResult { Fizz, Buzz, FizzBuzz, No }` (or whatever you choose to call them), and then you can just return a different string literal for each option inside `main`. – Robin Zigmond Jan 31 '23 at 21:55
  • @RobinZigmond good point. I'll write that up too -- sounds fun :) – Adam Smith Jan 31 '23 at 21:56
  • 1
    ha, you took it a bit further than I meant (returning the plain int back as part of the value when it fails to divide, and certainly implementing `Display` which it hadn't occurred to me to do rather than just using an ad-hoc function), but it looks good! – Robin Zigmond Jan 31 '23 at 22:30
  • You can also use `Cow`. – Chayim Friedman Feb 01 '23 at 08:57
3

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.)

Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
  • Since `"...".to_string()` is always the same, those could very well be constants like `FIZZ`, `BUZZ`, etc. – tadman Jan 31 '23 at 22:02