I want to write a function to parse text, but the text may come from external file or an internal &str
. The parse
function may go like this:
fn parse(lines: GenericLinesGenerator) {
for line in lines {
// parse content
}
}
... and it can be invoked like this:
use std::io::BufReader;
use std::fs::File;
let fin = BufReader::new(File::open("info.txt").expect("not found"));
parse(TransformFromIO(fin.lines()))
or
let content: &'static str = "some\nlong\ntext";
parse(TransformFromStr(content.lines()))
Is it possible to implement such a parse
function?
The two iterators don't produce the same values:
impl<B: BufRead> Iterator for io::Lines<B> {
type Item = Result<String>;
}
impl<'a> Iterator for str::Lines<'a> {
type Item = &'a str;
}
You have to handle that difference somehow. The most important difference is that io::Lines
can fail. Your program has to decide how to deal with that; I've chosen to just abort the program.
The next thing you need to do is to accept any type that can be converted into an iterator, and the value yielded from the iterator has to be converted to a type that you can deal with. It appears that &str
is the common denominator.
This is solved by using IntoIterator
and Borrow
:
use std::borrow::Borrow;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
fn parse<I>(lines: I)
where
I: IntoIterator,
I::Item: Borrow<str>,
{
for line in lines {
println!("line: {}", line.borrow());
}
}
fn main() {
parse("alpha\nbeta\ngamma".lines());
println!("----");
let f = File::open("/etc/hosts").expect("Couldn't open");
let b = BufReader::new(f);
parse(b.lines().map(|l| l.expect("Bad line!")));
}
Check The Rust Programming Language section on where
clauses for more information about trait bounds.
Using the Borrow
bound in the parse
function will allow you to borrow &str
, but if you need String
values, a better approach is to use Cow
.
Using line.borrow().to_string()
to obtain a String
value will always allocate, even when parse
is called with lines from file (in this case, lines.map
produces String
).
Using line.into_owned()
will allocate when called with lines from an &str
, but will not allocate when called with lines from file (will just unwrap the String
value passed to Cow::Owned
).
use std::borrow::Cow;
use std::io::{BufReader, BufRead};
use std::iter::IntoIterator;
use std::fs::File;
fn parse<'a, I>(lines: I)
where I: IntoIterator,
I::Item: Into<Cow<'a, str>>
{
for line in lines {
let line: Cow<'a, str> = line.into();
let line: String = line.into_owned();
// or
let line = line.into().into_owned()
println!("{}", line);
}
}
fn main() {
let fin = BufReader::new(File::open("/etc/hosts").expect("cannot open file"));
parse(fin.lines().map(|r| r.expect("file read failed")));
let content: &'static str = "some\nlong\ntext";
parse(content.lines());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With