nyctef
nyctef

Reputation: 571

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.

Upvotes: 1

Views: 820

Answers (2)

Masklinn
Masklinn

Reputation: 42332

When it's talking about the expected and found signatures, is it referring to the map parameter, or the passed-in function?

The two, Iterator::map sets the expectation and the function you pass in sets the "found" signature.

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.

It's an HRBT bound.

Here you can just ignore it entirely, it's just telling you that map expects a signature of (String) -> _, but the callback has a signature of (&_) -> _.

Apparently match needs to match on &strs rather than Strings, so I'm trying to use this answer to convert env::args(), like so:

The problem is that it's an answer to a different question: it starts from a Vec<String>.

This means it can start by calling ::iter from which it immediately gets a Iterator<Item=&String>, which is basically 99% of the way there, because it's already a borrow.

That's not an option with args, because Args is not a Vec, or even a collection, it's a iterator which gives you the ownership of the strings, there's nothing out there keeping them alive.

And thus if you do succeed in mapping from String to &str, you'll find yourself with the classic "borrowed value doesn't live long enough", because map will be returning a reference to something which is dropped at the end of the function.

Therefore if you really insist on working on slices of string slices, you need two steps: collect the Args to a Vec<String> to ensure they live, then map that to a Vec<&str>.

It would be much simpler to use one of the existing CLI-parsing libraries out there than do it by hand. And if you absolutely insist on doing it by hand, the normal way is to perform delegation and lazy collection (à la getopt), not to work with slices.

Upvotes: 1

Chayim Friedman
Chayim Friedman

Reputation: 71015

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.

Upvotes: 1

Related Questions