'Need help understanding error when converting env::args() to vec of &str

Rust newbie here: I'm trying to take arguments from the commandline and then match against them. Apparently match needs to match on &strs rather than Strings, so I'm trying to use this answer to convert env::args(), like so:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=be1e69216c971440315578c4579fcc63

let args = env::args().map(AsRef::as_ref).collect::<Vec<_>>();

match &args[1..] {
    ["hello"] => println!("Hello, world!"),
    // ...
    _ => eprintln!("unknown command: {:?}", args),
}

However, the as_ref part is failing to compile:

error[E0631]: type mismatch in function arguments
   --> src/main.rs:4:32
    |
4   |     let args = env::args().map(AsRef::as_ref).collect::<Vec<_>>();
    |                            --- ^^^^^^^^^^^^^
    |                            |   |
    |                            |   expected signature of `fn(String) -> _`
    |                            |   found signature of `for<'r> fn(&'r _) -> _`
    |                            required by a bound introduced by this call

I'm having trouble understanding this error:

  1. When it's talking about the expected and found signatures, is it referring to the map parameter, or the passed-in function?
  2. How do I understand for<'r> fn(&'r _) -> _? I've not seen this syntax before (especially the for part) so I don't know how to interpret the problem.


Solution 1:[1]

The "expected" signature is the signature of the map() parameter as declared, while the "found" signature is the signature of the function you passed. The map() function is declared as:

fn map<B, F>(self, f: F) -> Map<Self, F>?
where
    F: FnMut(Self::Item) -> B, 

env::args() returns env::Args, which implements Iterator<Item = String>. So the expected signature is some function (a type implementing FnMut, to be precise, so some closures are also accepted) of signature fn(String) -> SomeType.

Your function, on the other hand, is AsRef::as_ref, which is defined as:

pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

So it takes reference to some type (self) and returns reference to some another type (T). In your case, you want self to be String and T to be str, but the compiler doesn't know that yet: it has to infer that. From its point of view, it has the signature fn(&_) -> &_ (_ stands for "inference type", or "figure it out").

Or to be more precise for<'a> fn(&'a _) -> &'a _. This is called HRTB (Higher-Order Traits Bounds. See also How does for<> syntax differ from a regular lifetime bound?), but is not really relevant here. It's just the compiler got confused because it expected a function that takes String and got a function that takes some reference.

The solution, of course, is to use a function that takes String. You can use a closure for that. But that reveals another error (playground):

let args = env::args().map(|v| v.as_ref()).collect::<Vec<_>>();
error[E0515]: cannot return reference to function parameter `v`
 --> src/main.rs:4:36
  |
4 |     let args = env::args().map(|v| v.as_ref()).collect::<Vec<_>>();

Because the string is dropped at the end of the closure, but we return a reference to it as &str. There are multiple ways to fix that, depending on the use case. For example, if you only want to use the &str in the same function, you can collect the result of env::args() to a Vec:

let args = Vec::from_iter(env::args());
let args = args.iter().map(|v| v.as_ref()).collect::<Vec<_>>();

Playground.

By the way, because iter() returns an iterator over &String, now you can use AsRef::as_ref directly:

let args = Vec::from_iter(env::args());
let args = args.iter().map(AsRef::as_ref).collect::<Vec<_>>();

Playground.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chayim Friedman