4

I'd like to use map to iterate over an array and do stuff per item and get rid of the for loop. An error which I do not understand blocks my attempt. What I want to achieve is to iterate through a vector of i32 and match on them to concat a string with string literals and then return it at the end.

Function:

pub fn convert_to_rainspeak(prime_factors: Vec<i32>) -> String {
    let mut speak = String::new();
    prime_factors.iter().map(|&factor| {
        match factor {
            3 => { speak.push_str("Pling"); },
            5 => { speak.push_str("Plang"); },
            7 => { speak.push_str("Plong"); },
            _ => {}
        }
    }).collect();
    speak
}

fn main() {}

Output:

error[E0282]: type annotations needed
  --> src/main.rs:10:8
   |
10 |     }).collect();
   |        ^^^^^^^ cannot infer type for `B`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xetra11
  • 7,671
  • 14
  • 84
  • 159

4 Answers4

5

Iterator::collect is defined as:

fn collect<B>(self) -> B 
where
    B: FromIterator<Self::Item>

That is, it returns a type that is up to the caller. However, you have completely disregarded the output, so there's no way for it to infer a type. The code misuses collect when it basically wants to use for.

In your "fixed" version (which has since been edited, making this paragraph make no sense), you are being very inefficient by allocating a string in every iteration. Plus you don't need to specify any explicit types other than those on the function, and you should accept a &[i32] instead:

fn convert_to_rainspeak(prime_factors: &[i32]) -> String {
    prime_factors.iter()
        .map(|&factor| {
            match factor {
                3 => "Pling",
                5 => "Plang",
                7 => "Plong",
                _ => "",
            }
        })
        .collect()
}

fn main() {
    println!("{}", convert_to_rainspeak(&[1, 2, 3, 4, 5]));
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • _allocating strings_: should I use a constant &str and use its variable in the match? _what is &{T]_: Sorry I never saw this before - will look up the doc but mybe you have a good explanation as well – xetra11 Jun 16 '16 at 16:42
  • `&[T]` is [a *slice*](http://doc.rust-lang.org/stable/book/primitive-types.html#slices); the official documentation does a good job of describing it. I'm unclear what you mean by the constant and using it in the `match`. A string literal is a constant and has no allocation. Your old (pre-edit) code mapped each element to a `String`, not a `&str`, and then collected to another `String`. I was referring to the inner `String` with the extra allocations. – Shepmaster Jun 16 '16 at 16:57
1

The error in this case is that the compiler can't figure out what type you are collecting into. If you add a type annotation to collect, then it will work:

pub fn convert_to_rainspeak(prime_factors:Vec<i32>) -> String{
    let mut speak = String::new();
    prime_factors.iter().map(| &factor| {
        match factor {
            3 => {speak.push_str("Pling");},
            5 => {speak.push_str("Plang");},
            7 => {speak.push_str("Plong");},
            _ => {}
        }
    }).collect::<Vec<_>>();
    speak
}

However, this is really not the idiomatic way to do this. You should use for instead:

pub fn convert_to_rainspeak(prime_factors:Vec<i32>) -> String {
    let mut speak = String::new();
    for factor in prime_factors.iter() {
        match *factor {
            3 => speak.push_str("Pling"),
            5 => speak.push_str("Plang"),
            7 => speak.push_str("Plong"),
            _ => {}
        }
    }

    speak
}
Wesley Wiser
  • 9,491
  • 4
  • 50
  • 69
1

You could also use flat_map() instead of map(). This way you can map to Option and return None instead of empty string if there is no corresponding value.

fn convert_to_rainspeak(prime_factors: &[i32]) -> String {
    prime_factors
        .iter()
        .flat_map(|&factor| match factor {
            3 => Some("Pling"),
            5 => Some("Plang"),
            7 => Some("Plong"),
            _ => None,
        })
        .collect()
}


fn main() {
    println!("{}", convert_to_rainspeak(&[1, 2, 3, 7]));
}
lukwol
  • 264
  • 2
  • 6
0

few minutes later I solved it myself (any upgrade appreciated):

pub fn convert_to_rainspeak(prime_factors:Vec<i32>) -> String{
    let mut speak:String = prime_factors.iter().map(|&factor| {
        match factor {
            3 => {"Pling"},
            5 => {"Plang"},
            7 => {"Plong"},
            _ => {""}
        }
    }).collect();
    speak
}

The issue was that I was not aware that .collect() is awaiting the result of map. I then assigned prime_factors.iter()... to a string and rearranged var bindings so it now all works.

EDIT: refactored redundant assignments to the speak vector

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xetra11
  • 7,671
  • 14
  • 84
  • 159