0

I'm attempting to create pub fn sing(start: i32, end: i32) -> String that returns a concatenated string of the results of calling pub fn verse(num: i32) -> String repeatedly on each number between start and end.

I've googled for the answer and it seems that Rust String concatenation answers my question, and if I even write my code in playground it works, but:

my code:

pub fn verse(num: i32) -> String {
    match num {
        0 => "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n".to_string(),
        1 => "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n".to_string(),
        2 => "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n".to_string(),
        num => format!("{0} bottles of beer on the wall, {0} bottles of beer.\nTake one down and pass it around, {1} bottles of beer on the wall.\n",num,(num-1)),
    }
}

pub fn sing(start: i32, end: i32) -> String {
    (start..end).fold(String::new(), |ans, x| ans+&verse(x))
}

The problem is that

#[test]
fn test_song_8_6() {
    assert_eq!(beer::sing(8, 6), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n");
}

fails with beer::sing(8,6) returning "".

Community
  • 1
  • 1
Caleb Jasik
  • 3
  • 1
  • 4
  • Does this answer your question? [How to make a reverse ordered for loop?](https://stackoverflow.com/questions/25170091/how-to-make-a-reverse-ordered-for-loop) – Herohtar Mar 06 '22 at 02:59

1 Answers1

8

Your problem has nothing to do with string concatenation. It has to do with the fact that 8..6 is an empty iterator, because a range only iterates forwards. Because 8 >= 6, the iterator yields None on the first call to next.

fn main() {
    for i in 8..6 {
        println!("{}", i); // never reached
    }
}

This can be fixed by swapping start and end and calling rev() to iterate backwards.

fn main() {
    for i in (6..8).rev() {
        println!("{}", i);
    }
}

However, there's still another problem. In a range start..end, start is inclusive but end is exclusive. For example, the code above prints 7 and 6; 8 is not printed. See How do I include the end value in a range?

Putting it all together, sing should look like:

pub fn sing(start: i32, end: i32) -> String {
    (end..=start)
        .rev()
        .fold(String::new(), |ans, x| ans + &verse(x))
}

Note: Your test still fails because it expects two newlines between each verse, but your code only generates one. I'll leave this up to you to fix.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Thanks a lot. Why does range only iterate forward? – Caleb Jasik Mar 02 '17 at 02:57
  • 2
    @CalebJasik: It's not really that it only iterates forward, and more than it models a typical half-open range: `[start end)`. In this sense, `start == end` represents an empty range and `start >= end` a bug. It also makes the Range code simpler (and thus faster). For reverse iteration, you call `rev` explicitly, and you're done. – Matthieu M. Mar 02 '17 at 13:43