Reputation: 101
I have a fairly simple bit of code. I have a feeling I need to use a lifetime to accomplish this but I'm stumped right now.
parse_string
is a function that accepts a reference to a string, and returns a closure to be used later, here's the code:
fn main() {
let parse_this = parse_string(&String::from("Hello!"));
println!("{}", parse_this("goodbye!"));
}
fn parse_string(string: &String) -> impl Fn(&str) -> &String {
return |targetString| {
// pretend there is parsing logic
println!("{}", targetString);
return string;
};
}
Compiler error:
error: cannot infer an appropriate lifetime
--> src/main.rs:7:12
|
6 | fn parse_string(string: &String) -> impl Fn(&str) -> &String {
| ------------------------ this return type evaluates to the `'static` lifetime...
7 | return |targetString| {
| ____________^
8 | | // pretend there is parsing logic
9 | | println!("{}", targetString);
10 | | return string;
11 | | };
| |_____^ ...but this borrow...
|
note: ...can't outlive the anonymous lifetime #1 defined on the function body at 6:1
--> src/main.rs:6:1
|
6 | / fn parse_string(string: &String) -> impl Fn(&str) -> &String {
7 | | return |targetString| {
8 | | // pretend there is parsing logic
9 | | println!("{}", targetString);
10 | | return string;
11 | | };
12 | | }
| |_^
help: you can add a constraint to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the function body at 6:1
|
6 | fn parse_string(string: &String) -> impl Fn(&str) -> &String + '_ {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
--> src/main.rs:10:16
|
10 | return string;
| ^^^^^^
|
note: ...the reference is valid for the anonymous lifetime #2 defined on the body at 7:12...
--> src/main.rs:7:12
|
7 | return |targetString| {
| ____________^
8 | | // pretend there is parsing logic
9 | | println!("{}", targetString);
10 | | return string;
11 | | };
| |_____^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the function body at 6:1
--> src/main.rs:6:1
|
6 | / fn parse_string(string: &String) -> impl Fn(&str) -> &String {
7 | | return |targetString| {
8 | | // pretend there is parsing logic
9 | | println!("{}", targetString);
10 | | return string;
11 | | };
12 | | }
| |_^
Upvotes: 3
Views: 885
Reputation: 432169
You have a number of compounding issues:
You need an explicit lifetime to connect the lifetime of the string
argument to the lifetime of the return value of the returned closure. Right now, lifetime elision causes it to be inferred the same as the argument to the closure.
You cannot return a reference to the temporary through the function. It needs to be a distinct variable.
You have to move string
into the closure to prevent taking another reference to it, which wouldn't live long enough.
Additionally...
targetString
should be target_string
to follow Rust idioms.return
should not be used at the end of a block to follow Rust idioms.&str
is generally preferred to &String
fn main() {
let s = String::from("Hello!");
let parse_this = parse_string(&s);
println!("{}", parse_this("goodbye!"));
}
fn parse_string<'a>(string: &'a String) -> impl Fn(&str) -> &'a String {
return move |target_string| {
// pretend there is parsing logic
println!("{}", target_string);
string
};
}
See also:
Upvotes: 3
Reputation: 28075
You need to add an explicit lifetime annotation to parse_string
so that the compiler can tell which lifetimes are the same and which may be different.
Fn(&str) -> &String
would be the type for a function that returns a &String
of the same lifetime as the &str
passed in; i.e., for<'b> Fn(&'b str) -> &'b String
. You need to say that the &String
returned has the same lifetime as the &String
passed in to parse_string
:
fn parse_string<'a>(string: &'a String) -> impl Fn(&str) -> &'a String {
Note that Fn(&str)
doesn't have a lifetime annotation; this is because the lifetime of the &str
passed into the closure is unrelated to the lifetime of the &String
passed into parse_string
.
In order to make parse_string
compile, you need to make one more change. Closures try to borrow their environment if the compiler thinks it doesn't need to be moved. Your closure, which borrows string
, can't be returned from the function where string
is a local variable. To fix this, you move
the captured variable into the closure:
move |target_string| {
// pretend there is parsing logic
println!("{}", target_string);
string
}
It's idiomatic in Rust to omit the return
in the last expression in a function.
Also note that &String
is an unusual type because it offers no expressivity that &str
does not provide. It is almost always a mistake to have &String
in non-generic code. See Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument? for more information.
Putting it all together, here's how I'd write parse_string
:
fn parse_string<'a>(string: &'a str) -> impl Fn(&str) -> &'a str {
move |target_string| {
// pretend there is parsing logic
println!("{}", target_string);
string
}
}
Your main
also needs a small tweak: &String::from("Hello!")
takes a reference to a temporary String
that will be dropped immediately at the end of the line, invalidating the reference. This is easily fixed by storing the String
in a variable so it will not be dropped until the end of the scope:
fn main() {
let hello = String::from("Hello!");
let parse_this = parse_string(&hello);
println!("{}", parse_this("goodbye!"));
}
Upvotes: 2