Struct method type inference - rust

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.

Related

Cannot get wrapping of filter to compile

I have the goal of wrapping an Iterator<Item = rusb::Device<_> to Iterator<Item = LitraDevice>. The latter contains specific implementation.
To make this work I tried the following code:
use std::iter::Filter;
use rusb;
const VENDOR: u16 = 0x046d;
const PRODUCT: u16 = 0xc900;
struct LitraDevice {
dev: rusb::Device<rusb::GlobalContext>,
}
pub struct LitraDevices {
unfiltered: rusb::DeviceList<rusb::GlobalContext>,
}
struct LitraDeviceIterator<'a> {
it: Filter<rusb::Devices<'a, rusb::GlobalContext>, for<'r> fn(&'r rusb::Device<rusb::GlobalContext>) -> bool>,
}
impl LitraDevices {
pub fn new() -> Self {
let unfiltered = rusb::devices().unwrap();
LitraDevices { unfiltered }
}
fn can_not_handle<'r>(dev: &'r rusb::Device<rusb::GlobalContext>) -> bool {
let desc = dev.device_descriptor().unwrap();
match (desc.vendor_id(), desc.product_id()) {
(VENDOR, PRODUCT) => (),
_ => return true,
}
match desc.class_code() {
LIBUSB_CLASS_HID => return true, // Skip HID devices, they are handled directly by OS libraries
_ => return false,
}
}
pub fn iter<'a>(self) -> LitraDeviceIterator<'a> {
let it = self.unfiltered.iter().filter(Self::can_not_handle);
LitraDeviceIterator{
it,
}
}
}
impl <'a> Iterator for LitraDeviceIterator<'a> {
type Item = LitraDevice;
fn next(&mut self) -> Option<Self::Item> {
let n = self.it.next();
match n {
Some(Device) => return Some(LitraDevice{dev: n.unwrap()}),
None => return None,
}
}
}
Now I really cannot figure out how to code LitraDeviceIterator so that it wraps the filtered iterator.
All code iterations I have tried so far turn into a generic nightmare very quickly.
I rewrote your iter() to yield LitraDevice, you can surely take it wherever you wanted to go from there.
The first underlying issue is that filter() yields references, but in cases like these, you actually mean to move yielded items while filtering. That's what filter_map() is capable of. That way, you can scrap the references, greatly simplifying your code.
(This code does not work yet, read on)
pub fn iter(self) -> impl Iterator<Item = LitraDevice> {
self.unfiltered.iter().filter_map(|dev| {
(!Self::can_not_handle(&dev))
.then_some(dev)
.map(|dev| LitraDevice { dev })
})
}
Now, there's a second little issue at play her: rusb::DeviceList<T : UsbContext>>::iter(&self) returns rusb::Devices<'_, T>, '_ being the anonymous lifetime inferred from &self. Meaning, while you can drive rusb::Devices<'_, T> to yield Device<T>s, you can not actually keep it around longer than self.unfiltered. More specifically, as you consume self in iter(), you can not return an iterator referencing that rusb::Devices<'_, T> from iter(). One solution is to immediately collect, then again moving into an iterator.
pub fn iter(self) -> impl Iterator<Item = LitraDevice> {
let devices = self.unfiltered.iter().collect::<Vec<_>>();
devices.into_iter().filter_map(|dev| {
(!Self::can_not_handle(&dev))
.then_some(dev)
.map(|dev| LitraDevice { dev })
})
}

Function that generates a HashMap of Enum variants

I'm working with apollo_parser to parse a GraphQL query. It defines an enum, apollo_parser::ast::Definition, that has several variants including apollo_parser::ast::OperationDefintion and apollo_parser::ast::FragmentDefinition. I'd like to have a single Trait I can apply to apollo_parser::ast::Definition that provides a function definition_map that returns a HashMap mapping the operation name to the variant instance.
I've got as far as the trait, but I don't know how to implement it. Also, I don't know how to constrain T to be a variant of Definition.
trait Mappable {
fn definition_map<T>(&self) -> HashMap<String, T>;
}
EDIT:
Here's a Rust-ish pseudocode implementation.
impl Mappable for Document {
fn definition_map<T>(&self) -> HashMap<String, T> {
let defs = Vec<T> = self.definitions
.filter_map(|def: Definition| match def {
T(foo) => Some(foo),
_ => None
}).collect();
let map = HashMap::new();
for def: T in definitions {
map.insert(def.name(), def);
}
map
}
}
and it would output
// From a document consisting of OperationDefinitions "operation1" and "operation2"
// and FragmentDefinitons "fragment1" and "fragment2"
{
"operation1": OperationDefinition(...),
"operation2": OperationDefinition(...),
}
{
"fragment1": FragmentDefinition(...),
"fragment2": FragmentDefinition(...)
}
I don't know how to constrain T to be a variant of Definition.
There is no such thing in Rust. There's the name of the variant and the name of the type contained within that variant, there is no relationship between the two. The variants can be named whatever they want, and multiple variant can contain the same type. So there's no shorthand for pulling a T out of an enum which has a variant with a T.
You need to make your own trait that says how to get a T from a Definition:
trait TryFromDefinition {
fn try_from_def(definition: Definition) -> Option<Self> where Self: Sized;
fn name(&self) -> String;
}
And using that, your implementation is simple:
impl Mappable for Document {
fn definition_map<T: TryFromDefinition>(&self) -> HashMap<String, T> {
self.definitions()
.filter_map(T::try_from_def)
.map(|t| (t.name(), t))
.collect()
}
}
You just have to define TryFromDefinition for all the types you want to use:
impl TryFromDefinition for OperationDefinition {
fn try_from_def(definition: Definition) -> Option<Self> {
match definition {
Definition::OperationDefinition(operation) => Some(operation),
_ => None,
}
}
fn name(&self) -> String {
self.name().unwrap().ident_token().unwrap().text().into()
}
}
impl TryFromDefinition for FragmentDefinition {
fn try_from_def(definition: Definition) -> Option<Self> {
match definition {
Definition::FragmentDefinition(operation) => Some(operation),
_ => None,
}
}
fn name(&self) -> String {
self.fragment_name().unwrap().name().unwrap().ident_token().unwrap().text().into()
}
}
...
Some of this could probably be condensed using macros, but there's no normalized way that I can tell to get a name from a definition, so that would still have to be custom per type.
You should also decide how you want to handle definitions that don't have a name; you'd probably want to return Option<String> to avoid all those .unwrap()s, but I don't know how you'd want to put that in your HashMap.
Without knowing your whole workflow, I might suggest a different route instead:
struct Definitions {
operations: HashMap<String, OperationDefinition>,
fragments: HashMap<String, FragmentDefinition>,
...
}
impl Definitions {
fn from_document(document: &Document) -> Self {
let mut operations = HashMap::new();
let mut fragments = HashMap::new();
...
for definition in document.definitions() {
match definition {
Definition::OperationDefinition(operation) => {
let name: String = operation.name().unwrap().ident_token().unwrap().text().into();
operations.insert(name, operation);
},
Definition::FragmentDefinition(fragment) => {
let name: String = fragment.fragment_name().unwrap().name().unwrap().ident_token().unwrap().text().into();
fragments.insert(name, fragment);
},
...
}
}
Definitions {
operations,
fragments,
...
}
}
}

How do you convert an instance of generic T into a concrete instance in Rust?

I'm trying to implement this pattern:
use std::any::Any;
use std::fmt::Debug;
trait CommandHandler<TCommand> {
fn execute(&self, data: TCommand);
}
#[derive(Debug)]
struct FooCommand {}
struct FooCommandHandler {}
impl CommandHandler<FooCommand> for FooCommandHandler {
fn execute(&self, data: FooCommand) {
println!("Foo");
}
}
#[derive(Debug)]
struct BarCommand {}
struct BarCommandHandler {}
impl CommandHandler<BarCommand> for BarCommandHandler {
fn execute(&self, data: BarCommand) {
println!("Bar");
}
}
fn execute<T>(command: T)
where
T: Any + Debug,
{
println!("Command: {:?}", command);
match (&command as &Any).downcast_ref::<FooCommand>() {
Some(c) => (FooCommandHandler {}).execute(c),
None => {}
};
match (&command as &Any).downcast_ref::<BarCommand>() {
Some(c) => (BarCommandHandler {}).execute(c),
None => {}
};
}
fn main() {
(FooCommandHandler {}).execute(FooCommand {});
(BarCommandHandler {}).execute(BarCommand {});
execute(FooCommand {});
execute(BarCommand {});
}
This doesn't work:
error[E0308]: mismatched types
--> src/main.rs:37:51
|
37 | Some(c) => (FooCommandHandler {}).execute(c),
| ^ expected struct `FooCommand`, found &FooCommand
|
= note: expected type `FooCommand`
found type `&FooCommand`
error[E0308]: mismatched types
--> src/main.rs:41:51
|
41 | Some(c) => (BarCommandHandler {}).execute(c),
| ^ expected struct `BarCommand`, found &BarCommand
|
= note: expected type `BarCommand`
found type `&BarCommand`
How can I implement the execute() method in a way that preserves the following requirements:
The type XCommand should be totally naive of the XCommandHandler's that execute it.
Multiple implementations of CommandHandler<X> may exist.
The command handler receives (and consumes) the concrete command instance, not a reference to it (making duplicate dispatch of commands impossible).
In essence, I have a generic function fn foo<T>(v: T) and a I wish to dispatch to a number of concrete functions fn foo1(v: Foo), fn foo2(v: Bar); how do I do that?
Is transmute the only option?
Note that this is distinct from what Any::downcast_ref does, which is return an &Foo, not Foo from the generic value v.
You need to go via Box, like so:
fn execute<T>(command: T)
where
T: Any + Debug,
{
println!("Command: {:?}", command);
let any: Box<Any> = Box::new(command);
let any = match any.downcast() {
Ok(c) => return (FooCommandHandler {}).execute(*c),
Err(any) => any,
};
let any = match any.downcast() {
Ok(c) => return (BarCommandHandler {}).execute(*c),
Err(any) => any,
};
let _ = any; // avoid unused variable error
panic!("could not downcast command");
}
"But I don't wanna use a Box!"
Just use Box.
"But it's an allocation! I've measured the above code and proven beyond a shadow of a doubt that it's a bottleneck!"
What? Really?
"You can't prove otherwise."
Oh fine. But I do not guarantee that this will work in all cases. This is treading into "blow yourself up" territory. Do not do this unless you know you need to:
fn execute<T>(command: T)
where
T: Any + Debug,
{
use std::any::TypeId;
use std::mem;
println!("Command: {:?}", command);
macro_rules! do_cast {
($t:ty, $h:expr) => {
if TypeId::of::<T>() == TypeId::of::<$t>() {
let casted: $t = mem::transmute_copy(&command);
mem::forget(command); // we CANNOT let command drop.
$h.execute(casted);
return;
}
};
}
unsafe {
do_cast!(FooCommand, FooCommandHandler {});
do_cast!(BarCommand, BarCommandHandler {});
}
panic!("could not downcast command");
}
Just as a quick summary of the accepted answer:
Where &Any only has:
pub fn downcast_ref<T>(&self) -> Option<&T> where T: Any
Box<Any> implements:
pub fn downcast<T>(self) -> Result<Box<T>, Box<Any + 'static>> where T: Any
However, for complicated reasons, the documentation is on Box not on Any.

"Registering" trait implementations + factory method for trait objects

Say we want to have objects implementations switched at runtime, we'd do something like this:
pub trait Methods {
fn func(&self);
}
pub struct Methods_0;
impl Methods for Methods_0 {
fn func(&self) {
println!("foo");
}
}
pub struct Methods_1;
impl Methods for Methods_1 {
fn func(&self) {
println!("bar");
}
}
pub struct Object<'a> { //'
methods: &'a (Methods + 'a),
}
fn main() {
let methods: [&Methods; 2] = [&Methods_0, &Methods_1];
let mut obj = Object { methods: methods[0] };
obj.methods.func();
obj.methods = methods[1];
obj.methods.func();
}
Now, what if there are hundreds of such implementations? E.g. imagine implementations of cards for collectible card game where every card does something completely different and is hard to generalize; or imagine implementations for opcodes for a huge state machine. Sure you can argue that a different design pattern can be used -- but that's not the point of this question...
Wonder if there is any way for these Impl structs to somehow "register" themselves so they can be looked up later by a factory method? I would be happy to end up with a magical macro or even a plugin to accomplish that.
Say, in D you can use templates to register the implementations -- and if you can't for some reason, you can always inspect modules at compile-time and generate new code via mixins; there are also user-defined attributes that can help in this. In Python, you would normally use a metaclass so that every time a new child class is created, a ref to it is stored in the metaclass's registry which allows you to look up implementations by name or parameter; this can also be done via decorators if implementations are simple functions.
Ideally, in the example above you would be able to create Object as
Object::new(0)
where the value 0 is only known at runtime and it would magically return you an Object { methods: &Methods_0 }, and the body of new() would not have the implementations hard-coded like so "methods: [&Methods; 2] = [&Methods_0, &Methods_1]", instead it should be somehow inferred automatically.
So, this is probably extremely buggy, but it works as a proof of concept.
It is possible to use Cargo's code generation support to make the introspection at compile-time, by parsing (not exactly parsing in this case, but you get the idea) the present implementations, and generating the boilerplate necessary to make Object::new() work.
The code is pretty convoluted and has no error handling whatsoever, but works.
Tested on rustc 1.0.0-dev (2c0535421 2015-02-05 15:22:48 +0000)
(See on github)
src/main.rs:
pub mod implementations;
mod generated_glue {
include!(concat!(env!("OUT_DIR"), "/generated_glue.rs"));
}
use generated_glue::Object;
pub trait Methods {
fn func(&self);
}
pub struct Methods_2;
impl Methods for Methods_2 {
fn func(&self) {
println!("baz");
}
}
fn main() {
Object::new(2).func();
}
src/implementations.rs:
use super::Methods;
pub struct Methods_0;
impl Methods for Methods_0 {
fn func(&self) {
println!("foo");
}
}
pub struct Methods_1;
impl Methods for Methods_1 {
fn func(&self) {
println!("bar");
}
}
build.rs:
#![feature(core, unicode, path, io, env)]
use std::env;
use std::old_io::{fs, File, BufferedReader};
use std::collections::HashMap;
fn main() {
let target_dir = Path::new(env::var_string("OUT_DIR").unwrap());
let mut target_file = File::create(&target_dir.join("generated_glue.rs")).unwrap();
let source_code_path = Path::new(file!()).join_many(&["..", "src/"]);
let source_files = fs::readdir(&source_code_path).unwrap().into_iter()
.filter(|path| {
match path.str_components().last() {
Some(Some(filename)) => filename.split('.').last() == Some("rs"),
_ => false
}
});
let mut implementations = HashMap::new();
for source_file_path in source_files {
let relative_path = source_file_path.path_relative_from(&source_code_path).unwrap();
let source_file_name = relative_path.as_str().unwrap();
implementations.insert(source_file_name.to_string(), vec![]);
let mut file_implementations = &mut implementations[*source_file_name];
let mut source_file = BufferedReader::new(File::open(&source_file_path).unwrap());
for line in source_file.lines() {
let line_str = match line {
Ok(line_str) => line_str,
Err(_) => break,
};
if line_str.starts_with("impl Methods for Methods_") {
const PREFIX_LEN: usize = 25;
let number_len = line_str[PREFIX_LEN..].chars().take_while(|chr| {
chr.is_digit(10)
}).count();
let number: i32 = line_str[PREFIX_LEN..(PREFIX_LEN + number_len)].parse().unwrap();
file_implementations.push(number);
}
}
}
writeln!(&mut target_file, "use super::Methods;").unwrap();
for (source_file_name, impls) in &implementations {
let module_name = match source_file_name.split('.').next() {
Some("main") => "super",
Some(name) => name,
None => panic!(),
};
for impl_number in impls {
writeln!(&mut target_file, "use {}::Methods_{};", module_name, impl_number).unwrap();
}
}
let all_impls = implementations.values().flat_map(|impls| impls.iter());
writeln!(&mut target_file, "
pub struct Object;
impl Object {{
pub fn new(impl_number: i32) -> Box<Methods + 'static> {{
match impl_number {{
").unwrap();
for impl_number in all_impls {
writeln!(&mut target_file,
" {} => Box::new(Methods_{}),", impl_number, impl_number).unwrap();
}
writeln!(&mut target_file, "
_ => panic!(\"Unknown impl number: {{}}\", impl_number),
}}
}}
}}").unwrap();
}
The generated code:
use super::Methods;
use super::Methods_2;
use implementations::Methods_0;
use implementations::Methods_1;
pub struct Object;
impl Object {
pub fn new(impl_number: i32) -> Box<Methods + 'static> {
match impl_number {
2 => Box::new(Methods_2),
0 => Box::new(Methods_0),
1 => Box::new(Methods_1),
_ => panic!("Unknown impl number: {}", impl_number),
}
}
}

Delegating the creation of data structures

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.

Resources