Search code examples
rustcollect

Why does the compiler prevent me from using push on a Vec created using collect()?


The following compiles:

pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    let mut result = (0..list.len() - 1)
        .map(|i| format!("For want of a {} the {} was lost.", list[i], list[i + 1]))
        .collect::<Vec<String>>();
    result.push(format!("And all for the want of a {}.", list[0]));
    result.join("\n")
}

The following does not (see Playground):

pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    let mut result = (0..list.len() - 1)
        .map(|i| format!("For want of a {} the {} was lost.", list[i], list[i + 1]))
        .collect::<Vec<String>>()
        .push(format!("And all for the want of a {}.", list[0]))
        .join("\n");
    result
}

The compiler tells me

error[E0599]: no method named `join` found for type `()` in the current scope
 --> src/lib.rs:9:10
  |
9 |         .join("\n");
  |          ^^^^

I get the same type of error if I try to compose just with push.

What I would expect is that collect returns B, aka Vec<String>. Vec is not (), and Vec of course has the methods I want to include in the list of composed functions.

Why can't I compose these functions? The explanation might include describing the "magic" of terminating the expression after collect() to get the compiler to instantiate the Vec in a way that does not happen when I compose with push etc.


Solution

  • If you read the documentation for Vec::push and look at the signature of the method, you will learn that it does not return the Vec:

    pub fn push(&mut self, value: T)
    

    Since there is no explicit return type, the return type is the unit type (). There is no method called join on (). You will need to write your code in multiple lines.

    See also:


    I'd write this more functionally:

    use itertools::Itertools; // 0.8.0
    
    pub fn build_proverb(list: &[&str]) -> String {
        let last = list
            .get(0)
            .map(|d| format!("And all for the want of a {}.", d));
    
        list.windows(2)
            .map(|d| format!("For want of a {} the {} was lost.", d[0], d[1]))
            .chain(last)
            .join("\n")
    }
    
    fn main() {
        println!("{}", build_proverb(&["nail", "shoe"]));
    }
    

    See also: