Type inference failure / question


#1

Let’s say I have a struct like so:

struct Foo { names: Vec<String> }

impl Foo {
    fn parse(data: &str) -> Foo {
        // don't have to specify a type for names
        let names = data.split('|').map(str::to_owned).collect();
        Foo { names : names }
    }
}

Type inference seems to work to figure out that the local variable names is a Vec<String>. But if I try to use names, other than by assigning it into the struct literal, I get an error “the type of this value must be known in this context”.

For example, both of the following fail unless I write let names: Vec<_> = ...

fn parse_1(data: &str) -> Foo {
    let names = data.split('|').map(str::to_owned).collect();
    if names.len() < 2 {
        // the error occurs on the call to len
    }
    Foo { names : names }
}

fn parse_2(data: &str) -> Foo {
    let names = data.split('|').map(str::to_owned).collect();
    Foo { names : names.clone() }
    // the error now occurs on the call to clone
    if names.len() < 2 {
        // do something
    }
}

Is this a bug in type inference, or an expected limitation?


#2

edit: nevermind, I misread the code.


#3

Yes, but the problem is not with the .collect() method; in fact, the call to .collect() in the first version I gave of my parse function compiled and worked just fine without the hint.

In other words, assigning a variable created with .collect() to a struct member of type Vec<String> seems to be enough for rustc to know which version of .collect() to use without specifying the type of that variable; but it doesn’t seem to be enough to let it know how to handle other uses of that same variable (e.g., names.len() or names.clone()) in the same method.


#4

The obstacle is the .clone(). If you change names.clone() to Clone::clone(&names), type inference works again? Why? In both cases the compiler knows that the return value is of type Vec<String>. But names.clone() can mean different things depending on the type of names; for example, names could have a type that does not implement the Clone trait but has an inherent method named clone that happens to return a Vec<String>. Naming the Clone trait lets the compiler reason based on the declaration of Clone::clone, namely fn clone(&self) -> Self, which is enough to conclude that &names is an &Vec<String>, ergo names is a Vec<String>.

(In fact, Clone::clone(&names) can also work if names has a type other than Vec<String>, due to deref coercions. But the compiler is still okay with inferring Vec<String> in that case.)


#5

Thanks! Is the same kind of thing true for my parse_1 example, which does not use .clone() but uses .len()? e.g.

fn parse_1(data: &str) -> Foo {
    let names = data.split('|').map(str::to_owned).collect();
    if names.len() < 2 {
        // the error occurs on the call to len
    }
    Foo { names : names }
}

#6

I’ve done some experimenting, and I think I understand; type inference works “sequentially.” So this compiles:

let foo = "a|b".split('|').map(str::to_owned).collect(); // 1
let bar: Vec<String> = foo;                              // 2

and this compiles (because in line 3, rustc already knows it’s a Vec<String> from line 2):

let foo = "a|b".split('|').map(str::to_owned).collect(); // 1
let bar: &Vec<String> = &foo;                            // 2
if foo.len < 2 {}                                        // 3

but this doesn’t (because in line 2, rustc does not yet have the information from line 3 to tell it that foo is a Vec<String>).

let foo = "a|b".split('|').map(str::to_owned).collect(); // 1
if foo.len < 2 {}                                        // 2
let bar: &Vec<String> = &foo;                            // 3