Reputation: 571
Rust newbie here: I'm trying to take arguments from the commandline and then match against them. Apparently match
needs to match on &str
s rather than String
s, so I'm trying to use this answer to convert env::args()
, like so:
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:
map
parameter, or the passed-in function?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
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
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<_>>();
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<_>>();
Upvotes: 1