The following code works ( cargo +nightly run
) fine:
fn main() {
let res: Result<(), String> = Err(String::from("hi"));
println!("{}", res.map_err(shout).unwrap_err())
}
fn shout(s: String) -> String {
s.to_ascii_uppercase()
}
Clippy ( cargo +nightly clippy
) spits out a (justified) warning:
warning: this argument is passed by value, but not consumed in the function body
--> src/main.rs:6:13
|
6 | fn shout(s: String) -> String {
| ^^^^^^ help: consider changing the type to: `&str`
Changing the code to the suggested version
fn shout(s: &str) -> String {
s.to_ascii_uppercase()
}
results in a compiler error:
error[E0631]: type mismatch in function arguments
--> src/main.rs:3:24
|
3 | println!("{}", res.map_err(shout).unwrap_err())
| ^^^^^^^ expected signature of `fn(std::string::String) -> _`
...
6 | fn shout(s: &str) -> String {
| --------------------------- found signature of `for<'r> fn(&'r str) -> _`
What is the right way to react? Sure, I could simply do #![cfg_attr(feature="clippy", allow(needless_pass_by_value))]
but this feels wrong to me. Is there a way to use map_err
with the version of shout
taking a reference?
The best you can do is use a full closure:
res.map_err(|x| shout(&x)).unwrap_err()
Your original form has two steps needed to be able to work:
&String
to a &str
. Additionally, it needs to do both of those while the value is in scope, so that it doesn't end up with a dangling reference. Neither of these are things the "short" form of closures handles right now — the types must match exactly.
If you really wanted to avoid the closure, you can for this specific case:
res.as_ref().map_err(String::as_str).map_err(shout).unwrap_err()
// ^~~~~~ ^~~~~~~~~~~~~~
// | |
// | |- Convert `&String` to `&str`
// |
// |- Get a reference (`&String`)
I actually argued for the ability for your original code to work as part of the ergonomics initiative, but it did not seem to gain traction.
Like many problems in programming, you can "solve" this by adding more abstraction. Here, we introduce a trait to embody the concept of "an error that can be shouted":
fn main() {
let e1 = Err::<(), _>(String::from("hi"));
println!("{}", e1.map_err(ShoutyError::shout).unwrap_err());
let e2 = Err::<(), _>(42);
println!("{}", e2.map_err(ShoutyError::shout).unwrap_err());
}
trait ShoutyError {
fn shout(self) -> String;
}
impl ShoutyError for String {
fn shout(self) -> String {
self.to_ascii_uppercase()
}
}
impl ShoutyError for i32 {
fn shout(self) -> String {
format!("I YELL {}", self)
}
}
If you felt like you needed it, you could also have a wrapper function to keep the exact initial code:
fn shout<E: ShoutyError>(e: E) -> String {
e.shout()
}
I'd like to have a function
adapt
that takes one functionf : &T -> U
and returns a new functiong : T -> U
.
This is possible, but only in nightly Rust:
#![feature(conservative_impl_trait)]
fn adapt<F, T, U>(f: F) -> impl Fn(T) -> U
where
F: Fn(&T) -> U,
{
move |arg| f(&arg)
}
Unfortunately, it doesn't solve your problem because shout
doesn't accept a &String
and this would require str
to be a Sized
type.
The more verbose solution involves AsRef
:
#![feature(conservative_impl_trait)]
fn adapt<F, T1, T2, U>(f: F) -> impl Fn(T1) -> U
where
F: Fn(&T2) -> U,
T1: AsRef<T2>,
T2: ?Sized,
{
move |arg| f(arg.as_ref())
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.