Delegating the creation of data structures - rust

I’m very new to Rust. While trying out small things, I have written the following code. It simply scans files (given as arguments) for a specific string (“Started “) and prints out the matching lines:
use std::os;
use std::io::BufferedReader;
use std::io::File;
fn main() {
for target in os::args().iter() {
scan_file(target);
}
}
fn scan_file(path_str: &String) {
let path = Path::new(path_str.as_bytes());
let file = File::open(&path);
let mut reader = BufferedReader::new(file);
for line in reader.lines() {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
My question is: how can I refactor the function scan_file so that it looks something like this (or similar enough)?:
fn scan_file(path_str: &String) {
for line in each_line_in_file_with_path(path_str) {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
In this new version of the function, the three variable declarations are gone. Instead, the function each_line_in_file_with_path is expected to handle all the “turn a path into lines”, returning an iterator.
I’ve tried a number of things unsuccessfully, always due to variables going out of scope too early for my needs. I understand the problems I have (I think), but can’t find anywhere a good explanation of how this should be handled.

It is not possible to implement a working each_line_in_file_with_path function — at least, not without adding some overhead and unsafe code.
Let's look at the values involved and their types. First is path, of type Path (either posix::Path or windows::Path). The constructors for these types receive a BytesContainer by value, therefore they take ownership of it. No issues here.
Next is file, of type IoResult<File>. File::open() clones the path it receives, so again, no issues here.
Next is reader, of type BufferedReader<IoResult<File>>. Just like Path, the constructor for BufferedReader takes its argument by value and takes ownership of it.
The problem is with reader.lines(). This value is of type Lines<'r, T: 'r>. As the type signature suggests, this struct contains a borrowed reference. The signature of lines shows the relationship between the loaner and the borrower:
fn lines<'r>(&'r mut self) -> Lines<'r, Self>
How do we define each_line_in_file_with_path now? each_line_in_file_with_path cannot return a Lines directly. You probably tried writing the function like this:
fn each_line_in_file_with_path<'a, T>(path: &T) -> Lines<'a, BufferedReader<IoResult<File>>>
where T: BytesContainer {
let path = Path::new(path);
let file = File::open(&path);
let reader = BufferedReader::new(file);
reader.lines()
}
This gives a compilation error:
main.rs:46:5: 46:11 error: `reader` does not live long enough
main.rs:46 reader.lines()
^~~~~~
main.rs:42:33: 47:2 note: reference must be valid for the lifetime 'a as defined on the block at 42:32...
main.rs:42 where T: BytesContainer {
main.rs:43 let path = Path::new(path);
main.rs:44 let file = File::open(&path);
main.rs:45 let reader = BufferedReader::new(file);
main.rs:46 reader.lines()
main.rs:47 }
main.rs:42:33: 47:2 note: ...but borrowed value is only valid for the block at 42:32
main.rs:42 where T: BytesContainer {
main.rs:43 let path = Path::new(path);
main.rs:44 let file = File::open(&path);
main.rs:45 let reader = BufferedReader::new(file);
main.rs:46 reader.lines()
main.rs:47 }
error: aborting due to previous error
That's because we're trying to return a Lines that refers to a BufferedReader that ceases to exist when the function returns (the Lines would contain a dangling pointer).
Now, one might think, “I'll just return the BufferedReader along with the Lines”.
struct LinesInFileIterator<'a> {
reader: BufferedReader<IoResult<File>>,
lines: Lines<'a, BufferedReader<IoResult<File>>>
}
impl<'a> Iterator<IoResult<String>> for LinesInFileIterator<'a> {
fn next(&mut self) -> Option<IoResult<String>> {
self.lines.next()
}
}
fn each_line_in_file_with_path<'a, T>(path: &T) -> LinesInFileIterator<'a>
where T: BytesContainer {
let path = Path::new(path);
let file = File::open(&path);
let reader = BufferedReader::new(file);
LinesInFileIterator {
reader: reader,
lines: reader.lines()
}
}
This doesn't work either:
main.rs:46:16: 46:22 error: `reader` does not live long enough
main.rs:46 lines: reader.lines()
^~~~~~
main.rs:40:33: 48:2 note: reference must be valid for the lifetime 'a as defined on the block at 40:32...
main.rs:40 where T: BytesContainer {
main.rs:41 let path = Path::new(path);
main.rs:42 let file = File::open(&path);
main.rs:43 let reader = BufferedReader::new(file);
main.rs:44 LinesInFileIterator {
main.rs:45 reader: reader,
...
main.rs:40:33: 48:2 note: ...but borrowed value is only valid for the block at 40:32
main.rs:40 where T: BytesContainer {
main.rs:41 let path = Path::new(path);
main.rs:42 let file = File::open(&path);
main.rs:43 let reader = BufferedReader::new(file);
main.rs:44 LinesInFileIterator {
main.rs:45 reader: reader,
...
main.rs:46:16: 46:22 error: use of moved value: `reader`
main.rs:46 lines: reader.lines()
^~~~~~
main.rs:45:17: 45:23 note: `reader` moved here because it has type `std::io::buffered::BufferedReader<core::result::Result<std::io::fs::File, std::io::IoError>>`, which is non-copyable
main.rs:45 reader: reader,
^~~~~~
error: aborting due to 2 previous errors
Basically, we can't have a struct that contains a borrowed reference that points to another member of the struct, because when the struct is moved, the reference would become invalid.
There are 2 solutions:
Make a function that returns a BufferedReader from a file path, and call .lines() on it in your for loop.
Make a function that accepts a closure that receives each line.
fn main() {
for target in os::args().iter() {
scan_file(target.as_slice());
}
}
fn for_each_line_in_file_with_path_do(path: &str, action: |IoResult<String>|) {
let path = Path::new(path.as_bytes());
let file = File::open(&path);
let mut reader = BufferedReader::new(file);
for line in reader.lines() {
action(line);
}
}
fn scan_file(path_str: &str) {
for_each_line_in_file_with_path_do(path_str, |line| {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
});
}

You won't be able to do it without some boilerplate. You need to have some source of data, and because iterators return their data in chunks, they either have to contain the data or to have a reference into some other source of this data (this also includes iterators which return data from external source, e.g. lines in a file).
However, because you want to "encapsulate" your iterator into a function call, this iterator cannot be of the second kind, i.e. it cannot contain references, because all references it could contain would point to this function call stack. Consequently, the iterator's source can only be contained in this iterator.
And this is the boilerplate problem - in general there is no such iterator in the standard library. You will need to create it yourself. In this particular case, though, you can get away without implementing Iterator trait manually. You only need to create some simple structural wrapper:
use std::os;
use std::io::{BufferedReader, File, Lines};
fn main() {
for target in os::args().iter() {
scan_file(target.as_slice());
}
}
struct FileLines {
source: BufferedReader<File>
}
impl FileLines {
fn new(path_str: &str) -> FileLines {
let path = Path::new(path_str.as_bytes());
let file = File::open(&path).unwrap();
let reader = BufferedReader::new(file);
FileLines { source: reader }
}
fn lines(&mut self) -> Lines<BufferedReader<File>> {
self.source.lines()
}
}
fn scan_file(path_str: &str) {
for line in FileLines::new(path_str).lines() {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
(I also changed &String to &str because it is more idiomatic and general)
The FileLines structure owns the data and encapsulates all of the complex logic in its constructor. Then its lines() method just returns an iterator into its internals. This is rather common pattern in Rust, and usually you will be able to find the main owner of your data and build your program around it with methods which return iterators/references into this owner.
This is not exactly what you wanted (there are two function calls in for loop initializer - new() and lines()), but I believe that for all practical purposes they have the same expressiveness and usability.

Related

Make iterator of nested iterators

How could I pack the following code into a single iterator?
use std::io::{BufRead, BufReader};
use std::fs::File;
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));
for line in file.lines() {
for ch in line.expect("Unable to read line").chars() {
println!("Character: {}", ch);
}
}
Naively, I’d like to have something like (I skipped unwraps)
let lines = file.lines().next();
Reader {
line: lines,
char: next().chars()
}
and iterate over Reader.char till hitting None, then refreshing Reader.line to a new line and Reader.char to the first character of the line. This doesn't seem to be possible though because Reader.char depends on the temporary variable.
Please notice that the question is about nested iterators, reading text files is used as an example.
You can use the flat_map() iterator utility to create new iterator that can produce any number of items for each item in the iterator it's called on.
In this case, that's complicated by the fact that lines() returns an iterator of Results, so the Err case must be handled.
There's also the issue that .chars() references the original string to avoid an additional allocation, so you have to collect the characters into another iterable container.
Solving both issues results in this mess:
fn example() -> impl Iterator<Item=Result<char, std::io::Error>> {
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));
file.lines().flat_map(|line| match line {
Err(e) => vec![Err(e)],
Ok(line) => line.chars().map(Ok).collect(),
})
}
If String gave us an into_chars() method we could avoid collect() here, but then we'd have differently-typed iterators and would need to use either Box<dyn Iterator> or something like either::Either.
Since you already use .expect() here, you can simplify a bit by using .expect() within the closure to avoid handling the Err case:
fn example() -> impl Iterator<Item=char> {
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));
file.lines().flat_map(|line|
line.expect("Unable to read line").chars().collect::<Vec<_>>()
)
}
In the general case, flat_map() is usually quite easy. You just need to be mindful of whether you are iterating owned vs borrowed values; both cases have some sharp corners. In this case, iterating over owned String values makes using .chars() problematic. If we could iterate over borrowed str slices we wouldn't have to .collect().
Drawing on the answer from #cdhowie and this answer that suggests using IntoIter to get an iterator of owned chars, I was able to come up with this solution that is the closest to what I expected:
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, Lines};
use std::vec::IntoIter;
struct Reader {
lines: Lines<BufReader<File>>,
iter: IntoIter<char>,
}
impl Reader {
fn new(filename: &str) -> Self {
let file = BufReader::new(File::open(filename).expect("Unable to open file"));
let mut lines = file.lines();
let iter = Reader::char_iter(lines.next().expect("Unable to read file"));
Reader { lines, iter }
}
fn char_iter(line: io::Result<String>) -> IntoIter<char> {
line.unwrap().chars().collect::<Vec<_>>().into_iter()
}
}
impl Iterator for Reader {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
None => {
self.iter = match self.lines.next() {
None => return None,
Some(line) => Reader::char_iter(line),
};
Some('\n')
}
Some(val) => Some(val),
}
}
}
it works as expected:
let reader = Reader::new("src/main.rs");
for ch in reader {
print!("{}", ch);
}

How do I correctly pass this vector of file lines to my function in Rust?

I'm trying to read a file into a vector, then print out a random line from that vector.
What am I doing wrong?
I'm asking here because I know I'm making a big conceptual mistake, but I'm having trouble identifying exactly where it is.
I know the error -
error[E0308]: mismatched types
26 | processor(&lines)
| ^^^^^^ expected &str, found struct std::string::String
And I see that there's a mismatch - but I don't know how to give the right type, or refactor the code for that (very short) function.
My code is below:
use std::{
fs::File,
io::{prelude::*, BufReader},
path::Path,
};
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
fn processor(vectr: &Vec<&str>) -> () {
let vec = vectr;
let index = (rand::random::<f32>() * vec.len() as f32).floor() as usize;
println!("{}", vectr[index]);
}
fn main() {
let lines = lines_from_file("./example.txt");
for line in lines {
println!("{:?}", line);
}
processor(&lines);
}
While you're calling the processor function you're trying to pass a Vec<String> which is what the lines_from_file returns but the processor is expecting a &Vec<&str>. You can change the processor to match that expectation:
fn processor(vectr: &Vec<String>) -> () {
let vec = vectr;
let index = (rand::random::<f32>() * vec.len() as f32).floor() as usize;
println!("{}", vectr[index]);
}
The main function:
fn main() {
let lines = lines_from_file("./example.txt");
for line in &lines {. // &lines to avoid moving the variable
println!("{:?}", line);
}
processor(&lines);
}
More generally, a String is not the same as a string slice &str, therefore Vec<String> is not the same as Vec<&str>. I'd recommend checking the rust book: https://doc.rust-lang.org/nightly/book/ch04-03-slices.html?highlight=String#string-slices

Reading ZIP file in Rust causes data owned by the current function

I'm new to Rust and am likely have a huge knowledge gap. Basically, I'm hoping to be create a utility function that would except a regular text file or a ZIP file and return a BufRead where the caller can start processing line by line. It is working well for non ZIP files but I am not understanding how to achieve the same for the ZIP files. The ZIP files will only contain a single file within the archive which is why I'm only processing the first file in the ZipArchive.
I'm running into the the following error.
error[E0515]: cannot return value referencing local variable `archive_contents`
--> src/file_reader.rs:30:9
|
27 | let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();
| ---------------- `archive_contents` is borrowed here
...
30 | Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
It seems the archive_contents is preventing the BufRead object from returning to the caller. I'm just not sure how to work around this.
file_reader.rs
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
pub struct FileReader {
pub file_reader: Result<Box<BufRead>, &'static str>,
}
pub fn file_reader(filename: &str) -> Result<Box<BufRead>, &'static str> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => panic!(
"ERROR: Could not open file, {}: {}",
path.display(),
why.to_string()
),
};
if path.extension() == Some(OsStr::new("zip")) {
// Processing ZIP file.
let mut archive_contents: zip::read::ZipArchive<std::fs::File> =
zip::ZipArchive::new(file).unwrap();
let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();
// ERRORS: returns a value referencing data owned by the current function
Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
} else {
// Processing non-ZIP file.
Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
}
}
main.rs
mod file_reader;
use std::io::BufRead;
fn main() {
let mut files: Vec<String> = Vec::new();
files.push("/tmp/text_file.txt".to_string());
files.push("/tmp/zip_file.zip".to_string());
for f in files {
let mut fr = match file_reader::file_reader(&f) {
Ok(fr) => fr,
Err(e) => panic!("Error reading file."),
};
fr.lines().for_each(|l| match l {
Ok(l) => {
println!("{}", l);
}
Err(e) => {
println!("ERROR: Failed to read line:\n {}", e);
}
});
}
}
Any help is greatly appreciated!
It seems the archive_contents is preventing the BufRead object from returning to the caller. I'm just not sure how to work around this.
You have to restructure the code somehow. The issue here is that, well, the archive data is part of the archive. So unlike file, archive_file is not an independent item, it is rather a pointer of sort into the archive itself. Which means the archive needs to live longer than archive_file for this code to be correct.
In a GC'd language this isn't an issue, archive_file has a reference to archive and will keep it alive however long it needs. Not so for Rust.
A simple way to fix this would be to just copy the data out of archive_file and into an owned buffer you can return to the parent. An other option might be to return a wrapper for (archive_contents, item_index), which would delegate the reading (might be somewhat tricky though). Yet another would be to not have file_reader.
Thanks to #Masklinn for the direction! Here's the working solution using their suggestion.
file_reader.rs
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Cursor;
use std::io::Error;
use std::io::Read;
use std::path::Path;
use zip::read::ZipArchive;
pub fn file_reader(filename: &str) -> Result<Box<dyn BufRead>, Error> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => return Err(why),
};
if path.extension() == Some(OsStr::new("zip")) {
let mut archive_contents = ZipArchive::new(file)?;
let mut archive_file = archive_contents.by_index(0)?;
// Read the contents of the file into a vec.
let mut data = Vec::new();
archive_file.read_to_end(&mut data)?;
// Wrap vec in a std::io::Cursor.
let cursor = Cursor::new(data);
Ok(Box::new(cursor))
} else {
// Processing non-ZIP file.
Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
}
}
While the solution you have settled on does work, it has a few disadvantages. One is that when you read from a zip file, you have to read the contents of the file you want to process into memory before proceeding, which might be impractical for a large file. Another is that you have to heap allocate the BufReader in either case.
Another possibly more idiomatic solution is to restructure your code, such that the BufReader does not need to be returned from the function at all - rather, structure your code so that it has a function that opens the file, which in turn calls a function that processes the file:
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
pub fn process_file(filename: &str) -> Result<usize, String> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => return Err(format!(
"ERROR: Could not open file, {}: {}",
path.display(),
why.to_string()
)),
};
if path.extension() == Some(OsStr::new("zip")) {
// Handling a zip file
let mut archive_contents=zip::ZipArchive::new(file).unwrap();
let mut buf_reader = BufReader::with_capacity(128 * 1024,archive_contents.by_index(0).unwrap());
process_reader(&mut buf_reader)
} else {
// Handling a plain file.
process_reader(&mut BufReader::with_capacity(128 * 1024, file))
}
}
pub fn process_reader(reader: &mut dyn BufRead) -> Result<usize, String> {
// Example, just count the number of lines
return Ok(reader.lines().count());
}
fn main() {
let mut files: Vec<String> = Vec::new();
files.push("/tmp/text_file.txt".to_string());
files.push("/tmp/zip_file.zip".to_string());
for f in files {
match process_file(&f) {
Ok(count) => println!("File {} Count: {}", &f, count),
Err(e) => println!("Error reading file: {}", e),
};
}
}
This way, you don't need any Boxes and you don't need to read the file into memory before processing it.
A drawback to this solution would if you had multiple functions that need to be able to read from zip files. One way to handle that would be to define process_file to take a callback function to do the processing. First you would change the definition of process_file to be:
pub fn process_file<C>(filename: &str, process_reader: C) -> Result<usize, String>
where C: FnOnce(&mut dyn BufRead)->Result<usize, String>
The rest of the function body can be left unchanged. Now, process_reader can be passed into the function, like this:
process_file(&f, count_lines)
where count_lines would be the original simple function to count the lines, for instance.
This would also allow you to pass in a closure:
process_file(&f, |reader| Ok(reader.lines().count()))

error: `line` does not live long enough but it's ok in playground

I can't figure it out why my local var line does not live long enough. You can see bellow my code. It work on the Rust's playground.
I may have an idea of the issue: I use a structure (load is a function of this structure). As I want to store the result of the line in a member of my struct, it could be the issue. But I don't see what should I do to resolve this problem.
pub struct Config<'a> {
file: &'a str,
params: HashMap<&'a str, &'a str>
}
impl<'a> Config<'a> {
pub fn new(file: &str) -> Config {
Config { file: file, params: HashMap::new() }
}
pub fn load(&mut self) -> () {
let f = match fs::File::open(self.file) {
Ok(e) => e,
Err(e) => {
println!("Failed to load {}, {}", self.file, e);
return;
}
};
let mut reader = io::BufReader::new(f);
let mut buffer = String::new();
loop {
let result = reader.read_line(&mut buffer);
if result.is_ok() && result.ok().unwrap() > 0 {
let line: Vec<String> = buffer.split("=").map(String::from).collect();
let key = line[0].trim();
let value = line[1].trim();
self.params.insert(key, value);
}
buffer.clear();
}
}
...
}
And I get this error:
src/conf.rs:33:27: 33:31 error: `line` does not live long enough
src/conf.rs:33 let key = line[0].trim();
^~~~
src/conf.rs:16:34: 41:6 note: reference must be valid for the lifetime 'a as defined on the block at 16:33...
src/conf.rs:16 pub fn load(&mut self) -> () {
src/conf.rs:17 let f = match fs::File::open(self.file) {
src/conf.rs:18 Ok(e) => e,
src/conf.rs:19 Err(e) => {
src/conf.rs:20 println!("Failed to load {}, {}", self.file, e);
src/conf.rs:21 return;
...
src/conf.rs:31:87: 37:14 note: ...but borrowed value is only valid for the block suffix following statement 0 at 31:86
src/conf.rs:31 let line: Vec<String> = buffer.split("=").map(String::from).collect();
src/conf.rs:32
src/conf.rs:33 let key = line[0].trim();
src/conf.rs:34 let value = line[1].trim();
src/conf.rs:35
src/conf.rs:36 self.params.insert(key, value);
...
There are three steps in realizing why this does not work.
let line: Vec<String> = buffer.split("=").map(String::from).collect();
let key = line[0].trim();
let value = line[1].trim();
self.params.insert(key, value);
line is a Vec of Strings, meaning the vector owns the strings its containing. An effect of this is that when the vector is freed from memory, the elements, the strings, are also freed.
If we look at string::trim here, we see that it takes and returns a &str. In other words, the function does not allocate anything, or transfer ownership - the string it returns is simply a slice of the original string. So if we were to free the original string, the trimmed string would not have valid data.
The signature of HashMap::insert is fn insert(&mut self, k: K, v: V) -> Option<V>. The function moves both the key and the value, because these needs to be valid for as long as they may be in the hashmap. We would like to give the hashmap the two strings. However, both key and value are just references to strings which is owned by the vector - we are just borrowing them - so we can't give them away.
The solution is simple: copy the strings after they have been split.
let line: Vec<String> = buffer.split("=").map(String::from).collect();
let key = line[0].trim().to_string();
let value = line[1].trim().to_string();
self.params.insert(key, value);
This will allocate two new strings, and copy the trimmed slices into the new strings.
We could have moved the string out of the vector(ie. with Vec::remove), if we didn't trim the strings afterwards; I was unable to find a easy way of trimming a string without allocating a new one.
In addition, as malbarbo mentions, we can avoid the extra allocation that is done with map(String::from), and the creation of the vector with collect(), by simply omitting them.
In this case you have to use String instead of &str. See this to understand the difference.
You can also eliminate the creation of the intermediate vector and use the iterator return by split direct
pub struct Config<'a> {
file: &'a str,
params: HashMap<String, String>
}
...
let mut line = buffer.split("=");
let key = line.next().unwrap().trim().to_string();
let value = line.next().unwrap().trim().to_string();

Struct method type inference

Given the following:
use std::old_io::{BufferedReader, File};
struct Journal<T> where T: Buffer {
file: T,
}
impl<T: Buffer> Iterator for Journal<T> {
type Item = String;
fn next(&mut self) -> Option<String> {
match self.file.read_line() {
Ok(line) => Some(line.to_string()),
Err(_) => None,
}
}
}
fn main() {
let path = Path::new("/tmp/allocator-journal.txt");
let mut file = BufferedReader::new(File::open(&path));
let journal = Journal {file: file};
for line in journal {
print!("{}", line);
}
}
I would like to move the file opening logic into a new method on Journal. The following fails to compile due to unable to infer enough type information about '_'; type annotations required [E0282]:
use std::old_io::{BufferedReader, File, IoResult};
struct Journal<T> where T: Buffer {
file: T,
}
impl<T: Buffer> Journal<T> {
fn new() -> Journal<BufferedReader<IoResult<File>>> {
let path = Path::new("/tmp/allocator-journal.txt");
let mut file = BufferedReader::new(File::open(&path));
Journal {file: file}
}
}
impl<T: Buffer> Iterator for Journal<T> {
type Item = String;
fn next(&mut self) -> Option<String> {
match self.file.read_line() {
Ok(line) => Some(line.to_string()),
Err(_) => None,
}
}
}
fn main() {
let journal = Journal::new();
for line in journal {
print!("{}", line);
}
}
Neither adding type hints to the variable binding or the method call (Journal::new::<Journal<BufferedReader<etc..>>>) fix the problem.
Why can the type not be infered? The signature of Journal::new is explicit, right?
As an aside, why can't the return type of Journal::new() be Journal<T> where T = Buffer?
You are mixing the worlds of generics and not-generics (specifics?). Here's the fix:
impl Journal<BufferedReader<IoResult<File>>> {
fn new() -> Journal<BufferedReader<IoResult<File>>> {
let path = Path::new("/tmp/allocator-journal.txt");
let mut file = BufferedReader::new(File::open(&path));
Journal {file: file}
}
}
Note the lack of T here. The whole point is that you are deciding what type T must be (BufferedReader<IoResult<File>>), so there's no need for the type variable.
By having the type variable, the compiler is attempting to figure out what T should be. However, you don't use T anywhere, so it has nothing to connect the dots with, and you get an error stating as much.
This brings up the question: why have generics at all? You aren't actually using them for anything, so you might as well just replace T with BufferedReader<IoResult<File>> everywhere.

Resources