I'm having an issue with lifetimes that I'm not sure how to solve, since it seems like the change I'm making should be trivial with regards to lifetimes.
Given:
use anyhow::Context;
use nom::{IResult, bytes::complete::tag};
The following code compiles:
let input = std::str::from_utf8(&output.stdout).unwrap();
let mut lines = input.lines();
let branch_line = lines.next().context("no output from `git status`")?;
let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
let (branch, _) = branch.expect("failed to get name of current branch");
After changing the expect in the final line to context, the code no longer compiles:
let input = std::str::from_utf8(&output.stdout).unwrap();
let mut lines = input.lines();
let branch_line = lines.next().context("no output from `git status`")?;
let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
let (branch, _) = branch.context("failed to get name of current branch")?;
error[E0597]: `output.stdout` does not live long enough
--> src/status.rs:303:41
|
303 | let input = std::str::from_utf8(&output.stdout).unwrap();
| ^^^^^^^^^^^^^^ borrowed value does not live long enough
...
307 | let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
| ------------------- type annotation requires that `output.stdout` is borrowed for `'static`
...
436 | }
| - `output.stdout` dropped here while still borrowed
Looking at the docs for anyhow, it doesn't appear to me like it should introduce any lifetime bounds on the &output.stdout.
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
Scratching my head. Still new to lifetimes.
The problem is the type of branch, which is IResult<&str, &str>.
If you look at the implementation, you can see that this is an alias for IResult<&str, &str, nom::error::Error<&str>>, which again is an alias for Result<(&str, &str), nom::internal::err<nom::error::Error<&str>>>.
This seems complicated and all, but the main point I'm trying to make is that branch is a Result type, and the Err case of it has the type nom::internal::err<nom::error::Error<&str>>. With other words, the error carries a &str.
This is on purpose, because ownership is a big problem for nom. This is strongly related to Polonius problems. Nom solves this by returning ownership back through the Err type.
That sadly means that it is incompatible with anyhow. The error types of nom are meant to be consumed by nom, or at least manually converted into something else before raised into user code.
To explain the exact error you are getting:
output.stdout is a local variable
input, and everything else behind it, is referencing the data in output.stdout
The Err variant of branch is still referencing output.stdout
.context(), or to be more precise, the ? behind it, tries to return the Err variant out of the function, which fails because it still references output.stdout, and the reference would then outlive the data it references.
This is not a problem for next().context(), because the None value of next() does not carry a reference to output.stdout.
One way to fix this is to break the reference by converting the &str from the Err type to an owned String:
use anyhow::{Context, Result};
use nom::{bytes::complete::tag, IResult};
fn main() -> Result<()> {
let input = "aaaaaa".to_string();
let mut lines = input.lines();
let branch_line = lines.next().context("no output from `git status`")?;
let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
let (branch, _) = branch
.map_err(|e| e.to_owned())
.context("failed to get name of current branch")?;
Ok(())
}
Related
This code draws four red dots in a picture.
use plotters::chart::{DualCoordChartContext, ChartContext, SeriesAnno};
use plotters::coord::types::RangedCoordf32;
use plotters::prelude::*;
use plotters::coord::Shift;
type CC<'a> = ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordf32, RangedCoordf32>>;
//type CCBAD = ChartContext<BitMapBackend, Cartesian2d<RangedCoordf32, RangedCoordf32>>;
const OUT_FILE_NAME: &'static str = "sample.png";
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let root_area: DrawingArea<BitMapBackend, Shift> =
BitMapBackend::new(OUT_FILE_NAME, (400, 400)).into_drawing_area();
let mut cb: ChartBuilder<BitMapBackend> =
ChartBuilder::on(&root_area);
let mut cc: ChartContext<BitMapBackend, Cartesian2d<RangedCoordf32, RangedCoordf32>> =
cb.build_cartesian_2d(0.0f32..5.0f32, 0.0f32..5.0f32)?;
let series: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
cc.draw_series(PointSeries::of_element(
series.iter().map(|x| (*x, *x)), 3,ShapeStyle::from(&RED).filled(),
&|coord, size, style| { EmptyElement::at(coord) + Circle::new((0, 0), size, style) },
))?;
Ok(())
}
I put the explicit types of variables on purpose (because why not?).
Soo... the types seem a bit long. How about we make shorter names.
Let's start with ChartContext. The compiler only lets me make the type type CC<'a>. CCBAD has missing lifetimes.
But if I try to use it like this:
let mut cc: CC = // ChartContext<BitMapBackend, Cartesian2d<RangedCoordf32, RangedCoordf32>> =
cb.build_cartesian_2d(0.0f32..5.0f32, 0.0f32..5.0f32)?;
It's suddenly a problem! Why?
16 | ChartBuilder::on(&root_area);
| ^^^^^^^^^^ borrowed value does not live long enough
...
25 | }
| -
| |
| `root_area` dropped here while still borrowed
| borrow might be used here, when `root_area` is dropped and runs the destructor for type `plotters::drawing::DrawingArea<plotters::prelude::BitMapBackend<'_>, Shift>`
Another story is trying to put the "draw_series" call into a function. Basically it ends up with the same error message as here.
Why I can specify the type ChartContext<BitMapBackend, Cartesian2d<RangedCoordf32, RangedCoordf32>> but cannot make a 'type' definition with it. Why that weird error?
By reusing the same lifetime twice you're forcing 2 lifetimes to be the same that shouldn't be.
Use this instead:
type CC<'a, 'b> = ChartContext<'a, BitMapBackend<'b>, Cartesian2d<RangedCoordf32, RangedCoordf32>>;
The purpose of my program is to read questions/answers from a file (line by line), and create several structs from it, put into a Vec for further processing.
I have a rather long piece of code, which I tried to separate into several functions (full version on Playground; hopefully is valid link).
I suppose I'm not understanding a lot about borrowing, lifetimes and other things. Apart from that, the given examples from all around I've seen, I'm not able to adapt to my given problems.
Tryigin to remodel my struct fields from &str to String didn't change anything. As it was with creating Vec<Question> within get_question_list.
Function of concern is as follows:
fn get_question_list<'a>(mut questions: Vec<Question<'a>>, lines: Vec<String>) -> Vec<Question<'a>> {
let count = lines.len();
for i in (0..count).step_by(2) {
let q: &str = lines.get(i).unwrap();
let a: &str = lines.get(i + 1).unwrap();
questions.push(Question::new(q, a));
}
questions
}
This code fails with the compiler as following (excerpt):
error[E0597]: `lines` does not live long enough
--> src/main.rs:126:23
|
119 | fn get_question_list<'a>(mut questions: Vec<Question<'a>>, lines: Vec<String>) -> Vec<Question<'a>> {
| -- lifetime `'a` defined here
...
126 | let a: &str = lines.get(i + 1).unwrap();
| ^^^^^ borrowed value does not live long enough
127 |
128 | questions.push(Question::new(q, a));
| ----------------------------------- argument requires that `lines` is borrowed for `'a`
...
163 | }
| - `lines` dropped here while still borrowed
Call to get_question_list is around:
let lines: Vec<String> = content.split("\n").map(|s| s.to_string()).collect();
let counter = lines.len();
if counter % 2 != 0 {
return Err("Found lines in quiz file are not even (one question or answer is missing.).");
}
questions = get_question_list(questions, lines);
Ok(questions)
The issue is that your Questions are supposed to borrow something (hence the lifetime annotation), but lines gets moved into the function, so when you create a new question from a line, it's borrowing function-local data, which is going to be destroyed at the end of the function. As a consequence, the questions you're creating can't escape the function creating them.
Now what you could do is not move the lines into the function: lines: &[String] would have the lines be owned by the caller, which would "fix" get_question_list.
However the exact same problem exists in read_questions_from_file, and there it can not be resolved: the lines are read from a file, and thus are necessarily local to the function (unless you move the lines-reading to main and read_questions_from_file only borrows them as well).
Therefore the simplest proper fix is to change Question to own its data:
struct Question {
question: String,
answer: String
}
This way the question itself keeps its data alive, and the issue goes away.
We can improve things further though, I think:
First, we can strip out the entire mess around newlines by using String::lines, it will handle cross-platform linebreaks, and will strip them.
It also seems rather odd that get_question_list takes a vector by value only to append to it and immediately return it. A more intuitive interface would be to either:
take the "output vector" by &mut so the caller can pre-size or reuse it across multiple loads, which doesn't really seem useful in this case
or create the output vector internally, which seems like the most sensible case here
Here is what I would consider a more pleasing version: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c0d440d67654b92c75d136eba2bba0c1
fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
let file_content = read_file(filename)?;
let lines: Vec<_> = file_content.lines().collect();
if lines.len() % 2 != 0 {
return Err(Box::new(OddLines));
}
let mut questions = Vec::with_capacity(lines.len() / 2);
for chunk in lines.chunks(2) {
if let [q, a] = chunk {
questions.push(Question::new(q.to_string(), a.to_string()))
} else {
unreachable!("Odd lines should already have been checked");
}
}
Ok(questions)
}
Note that I inlined / removed get_question_list as I don't think it pulls its weight at this point, and it's both trivial and very specific.
Here is a variant which works similarly but with different tradeoffs: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3b8f95aef5bcae904545617749086dbc
fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
let file_content = read_file(filename)?;
let mut lines = file_content.lines();
let mut questions = Vec::new();
while let Some(q) = lines.next() {
let a = lines.next().ok_or(OddLines)?;
questions.push(Question::new(q.to_string(), a.to_string()));
}
Ok(questions)
}
it avoids collecting the lines to a Vec, but as a result has to process the file to the end before it knows that said file is suitable, and it can't preallocate Questions.
At this point, because we do not care for lines being a Vec anymore, we could operate on a BufRead and strip out read_file as well:
fn read_questions_from_file(filename: &str) -> Result<Vec<Question>, Box<dyn Error>> {
let file_content = BufReader::new(File::open(filename)?);
let mut lines = file_content.lines();
let mut questions = Vec::new();
while let Some(q) = lines.next() {
let a = lines.next().ok_or(OddLines)?;
questions.push(Question::new(q?, a?));
}
Ok(questions)
}
The extra ? are because while str::Lines yields &str, io::Lines yields Result<String, io::Error>: IO errors are reported lazily when a read is attempted, meaning every line-read could report a failure if read_to_string would have failed.
OTOH since io::Lines returns a Result<String, ...> we can use q and a directly without needing to convert them to String.
I have a very small repo which currently does not compile, with the following error:
error[E0597]: `line` does not live long enough
--> src/main.rs:19:20
|
19 | let line = line.trim();
| ^^^^-------
| |
| borrowed value does not live long enough
| argument requires that `line` is borrowed for `'static`
...
22 | }
| - `line` dropped here while still borrowed
I've had trouble producing a minimal example demoing the problem: this (playground) works as expected, despite that fn main is identical to the one which fails, as are the signatures of ItemParser::parse and Item::pretty_to.
Inlining a section of fn main, and some signatures:
let parser = ItemParser::new();
let stdin = stdin();
let reader = stdin.lock();
let reader = BufReader::new(reader);
let stdout = stdout();
let mut writer = stdout.lock();
for line in reader.lines() {
let line = line?;
let line = line.trim();
let item = parser.parse(line)?;
item.pretty_to(&mut writer)?;
}
The same issue persists when I comment out item.pretty_to(&mut writer)?;, so I believe that that isn't the problem.
I can't show the actual code for ItemParser as it's generated by LALRPOP, but the signature as reported by rustdoc is
pub struct ItemParser { /* fields omitted */ }
pub fn parse<'input>(
&self,
input: &'input str
) -> Result<Item<'input>, ParseError<usize, Token<'input>, &'static str>>
Unlike this issue, nothing in this crate explicitly requires a 'static lifetime.
My expectation is that at the head of the for loop, item has type io::Result<String>. After we discard the error and trim the edges, it should have type &'a str, where 'a is the lifetime scoped to this iteration of the for loop. In that case, parsing the line should produce an Item<'a> with the same lifetime. It drops before the lifetime ends, in appropriate sequence. As nothing visibly requests a 'static lifetime, I don't know where that requirement is coming from.
On error, parser.parse() yields a type that is bounded to the lifetime of the input.
Result<Item<'input>, ParseError<usize, Token<'input>, &'static str>>
// ^^^^^^^^^^^^^
You're using ? to return the error from main (not "discard" it), which by necessity will outlive the loop, and therefore line.
You can handle the error immediately via match or if let, or do something like parser.parse().map_err(...)? to transform the error into something not bounded to the lifetime of line.
Answering the title specifically, The 'static requirement is from using eyre::Result which is an alias for Result<T, E = Report>. Report can be created from any Error type (which ParseError is), but is constrained to be 'static. It is a shame the compiler doesn't bring this up though.
Since str is a sort of slice, it can not outlive its parent. The result on trim is really just a slice the original string.
pub fn trim(&self) -> &str
// Now with the implicit lifetimes
pub fn trim<'a, 'b: 'a>(&'b self) -> &'a str
Since none of the lifetimes of the function arguments would not make appropriate bounds for the lifetime of the function output, it must have a different lifetime and the only appropriate lifetime for an unconstrained reference is 'static.
Its basically the equivalent to returning a reference to data you just freed in c.
Foo *data_to_return = &foo->bar;
free(foo);
return data_to_return;
But it's kinda a moot point anyway because the function would never work in the first place. The lifetime is more of a symptom of your issue than the cause. Just ignore the lifetime for now since it sounds like its holding you up from actually fixing the issue. Once you fix your code, the 'static bound will go away.
I am really new to Rust, I am having trouble solving this error, but it only happens if I comment out the while statement , basicly I am asking values from the console and storing it in a HashMap:
use std::collections::HashMap;
use std::io;
fn main() {
let mut customers = HashMap::new();
let mut next_customer = true;
while next_customer {
let mut input_string = String::new();
let mut temp_vec = Vec::with_capacity(3);
let mut vec = Vec::with_capacity(2);
println!("Insert new customer f.e = customer id,name,address:");
io::stdin().read_line(&mut input_string);
input_string = input_string.trim().to_string();
for s in input_string.split(",") {
temp_vec.push(s);
}
vec.push(temp_vec[1]);
vec.push(temp_vec[2]);
let mut key_value = temp_vec[0].parse::<i32>().unwrap();
customers.insert(key_value, vec);
next_customer = false;
}
println!("DONE");
}
The code results in the error
error[E0597]: `input_string` does not live long enough
--> src/main.rs:14:18
|
14 | for s in input_string.split(",") {
| ^^^^^^^^^^^^ borrowed value does not live long enough
...
20 | customers.insert(key_value, vec);
| --------- borrow later used here
21 | next_customer = false;
22 | }
| - `input_string` dropped here while still borrowed
As others have said the problem lies with the lifetime and/or type of the values getting put into the customers map.
customers.insert(key_value, vec);
| --------- borrow later used here
Often this happens when the compiler has decided to give an object a type that you didn't expect. To find out what it's doing you can force the type, and see how it complains. Changing the code to:
let mut customers: HashMap<(),()> = HashMap::new();
Gives us two relevant errors:
20 | customers.insert(key_value, vec);
| ^^^^^^^^^ expected `()`, found `i32`
...
20 | customers.insert(key_value, vec);
| ^^^ expected `()`, found struct `std::vec::Vec`
|
= note: expected unit type `()`
found struct `std::vec::Vec<&str>`
So the type that the compiler wants to give our customers object is HashMap<i32, Vec<&str>>
The problem is that the &str lifetime has got to be inside the block as we don't store the Strings anywhere, and they can't have 'static lifetime since they're user input.
This means we probably want a HashMap<i32,Vec<String>>.
Changing the code to use one of those gives us an error about vec not having the right type: It's getting deduced as a Vec<&str>, but we want a Vec<String>.
We have two options.
Convert the vec to the right type just before we insert it into the map using customers.insert(key_value, vec.iter().map(|s| s.to_string()).collect()). (Though you may want to extract it to a variable for clarity).
Explicitly change the type of vec to Vec<String>
Option 1 "just works". While option 2 leads us down a path of making similar changes closer and closer to the read_line call.
Once you've decided on the fix in option 1, you can remove the manual type annotations that were added to work out the fix, if you find them overly noisy.
The issue is that you are passing around reference to underlying &str values that will get dropped. One way is to take the input string, trim and split it, then clone it going into the other vector.
let temp_vec: Vec<String> = input_string.trim().split(",").map(|t| t.to_string()).collect();
vec.push(temp_vec[1].clone());
vec.push(temp_vec[2].clone());
Editor's note: This question was asked before Rust 1.0. Since then, many functions and types have changed, as have certain language semantics. The code in the question is no longer valid, but the ideas expressed in the answers may be.
I'm trying to list the files in a directory and copy the filename to my own Vec. I've tried several solutions, but it always ends up with a problem of not being able to create long enough living variables. I don't understand my mistake.
fn getList(action_dir_path : &str) -> Vec<&str> {
let v = fs::readdir(&Path::new(action_dir_path))
.unwrap()
.iter()
.map(|&x| x.filestem_str().unwrap())
.collect();
return v;
}
Why does the compiler complain about "x" ? I don't care about x, I want the &str inside it and I thought &str were static.
I tried this way, but I got the same result with the compiler complaining about "paths" not living long enough.
fn getList2(action_dir_path : &str) -> Vec<&str> {
let paths = fs::readdir(&Path::new(action_dir_path)).unwrap();
let mut v : Vec<&str> = Vec::new();
for path in paths.iter(){
let aSlice = path.filestem_str().unwrap();
v.push(aSlice);
}
return v;
}
Here is the playground.
The most literal translation of your code that supports Rust 1.0 is this:
use std::{fs, path::Path, ffi::OsStr};
fn getList(action_dir_path: &str) -> Vec<&OsStr> {
let v = fs::read_dir(&Path::new(action_dir_path))
.unwrap()
.map(|x| x.unwrap().path().file_stem().unwrap())
.collect();
return v;
}
This produces the error messages:
Rust 2015
error[E0597]: borrowed value does not live long enough
--> src/lib.rs:6:18
|
6 | .map(|x| x.unwrap().path().file_stem().unwrap())
| ^^^^^^^^^^^^^^^^^ - temporary value only lives until here
| |
| temporary value does not live long enough
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 3:1...
--> src/lib.rs:3:1
|
3 | / fn getList(action_dir_path: &str) -> Vec<&OsStr> {
4 | | let v = fs::read_dir(&Path::new(action_dir_path))
5 | | .unwrap()
6 | | .map(|x| x.unwrap().path().file_stem().unwrap())
7 | | .collect();
8 | | return v;
9 | | }
| |_^
Rust 2018
error[E0515]: cannot return value referencing temporary value
--> src/lib.rs:6:18
|
6 | .map(|x| x.unwrap().path().file_stem().unwrap())
| -----------------^^^^^^^^^^^^^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| temporary value created here
The problem comes from Path::file_stem. This is the signature:
pub fn file_stem(&self) -> Option<&OsStr>
This indicates that the method will return a borrowed reference to a OsStr. The PathBuf struct is the owner of the string. When you leave the method, there's nowhere left that owns the PathBuf, so it will be dropped. This means that any references into the PathBuf will no longer be valid. This is Rust preventing you from having references to memory that is no longer allocated, yay for Rust!
The easiest thing you can do is return a Vec<String>. String owns the string inside of it, so we don't need to worry about it being freed when we leave the function:
fn get_list(action_dir_path: &str) -> Vec<String> {
fs::read_dir(action_dir_path)
.unwrap()
.map(|x| {
x.unwrap()
.path()
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string()
})
.collect()
}
I also updated the style (at no charge!) to be more Rust-like:
Use snake_case for items
No space before the colon in type definitions
There's no reason to set a variable just to return it.
Don't use explicit return statements unless you are exiting from a function early.
There's no need to wrap the path in a Path.
However, I'm not a fan of all of the unwrapping. I'd write the function like this:
use std::{ffi::OsString, fs, io, path::Path};
fn get_list(action_dir_path: impl AsRef<Path>) -> io::Result<Vec<OsString>> {
fs::read_dir(action_dir_path)?
.map(|entry| entry.map(|e| e.file_name()))
.collect()
}
fn main() {
println!("{:?}", get_list("/etc"));
}
In addition to the changes above:
I use a generic type for the input path.
I return a Result to propagate errors to the caller.
I directly ask the DirEntry for the filename.
I leave the type as an OsString.
One small related point:
I thought &str were static.
&'static strs are static, but that's only one kind of &str. It can have any kind of lifetime.