2016-08-05 20 views
7

Tôi mới vào Rust và đọc Các Rust Programming Language, và trong Lỗi Xử lý phần there is a "case study" mô tả một chương trình để đọc dữ liệu từ một tập tin CSV bằng cách sử dụng csvrustc-serialize thư viện (sử dụng getopts để phân tích cú pháp đối số).Return iterator lười biếng mà phụ thuộc vào dữ liệu được phân bổ trong chức năng

Tác giả viết hàm search mà bước qua các hàng của tệp csv bằng cách sử dụng đối tượng csv::Reader và thu thập các mục có trường 'thành phố' khớp với giá trị được chỉ định vào vectơ và trả về. Tôi đã thực hiện một cách tiếp cận hơi khác so với tác giả, nhưng điều này không ảnh hưởng đến câu hỏi của tôi. chức năng (làm việc) của tôi trông như thế này:

extern crate csv; 
extern crate rustc_serialize; 

use std::path::Path; 
use std::fs::File; 

fn search<P>(data_path: P, city: &str) -> Vec<DataRow> 
    where P: AsRef<Path> 
{ 
    let file = File::open(data_path).expect("Opening file failed!"); 
    let mut reader = csv::Reader::from_reader(file).has_headers(true); 

    reader.decode() 
      .map(|row| row.expect("Failed decoding row")) 
      .filter(|row: &DataRow| row.city == city) 
      .collect() 
} 

nơi loại DataRow chỉ là một kỷ lục,

#[derive(Debug, RustcDecodable)] 
struct DataRow { 
    country: String, 
    city: String, 
    accent_city: String, 
    region: String, 
    population: Option<u64>, 
    latitude: Option<f64>, 
    longitude: Option<f64> 
} 

Bây giờ, tác giả đặt ra, là "tập thể dục cho người đọc" sợ hãi, vấn đề sửa đổi hàm này để trả về một trình lặp thay vì một vectơ (loại bỏ cuộc gọi đến collect). Câu hỏi của tôi là: Làm thế nào điều này có thể được thực hiện ở tất cả, và những cách súc tích và thành ngữ nhất để làm điều đó là gì?


Một nỗ lực đơn giản mà tôi nghĩ được chữ ký đúng loại là

fn search_iter<'a,P>(data_path: P, city: &'a str) 
    -> Box<Iterator<Item=DataRow> + 'a> 
    where P: AsRef<Path> 
{ 
    let file = File::open(data_path).expect("Opening file failed!"); 
    let mut reader = csv::Reader::from_reader(file).has_headers(true); 

    Box::new(reader.decode() 
        .map(|row| row.expect("Failed decoding row")) 
        .filter(|row: &DataRow| row.city == city)) 
} 

Tôi trả về một đối tượng đặc điểm của loại Box<Iterator<Item=DataRow> + 'a> để không phải tiếp xúc với các nội Filter loại, và nơi mà các đời 'a được giới thiệu chỉ để tránh phải tạo bản sao cục bộ city. Nhưng điều này không biên dịch được vì reader không sống đủ lâu; nó được cấp phát trên ngăn xếp và do đó được deallocated khi hàm trả về.

Tôi đoán điều này có nghĩa là reader phải được cấp phát trên heap (tức là đóng hộp) ngay từ đầu hoặc bằng cách nào đó đã di chuyển khỏi ngăn xếp trước khi chức năng kết thúc. Nếu tôi đã trả lại một sự đóng cửa, đây chính xác là vấn đề sẽ được giải quyết bằng cách làm cho nó đóng cửa move. Nhưng tôi không biết làm thế nào để làm một cái gì đó tương tự khi tôi không trả lại một chức năng. Tôi đã thử xác định một loại trình vòng lặp tùy chỉnh có chứa dữ liệu cần thiết, nhưng tôi không thể làm cho nó hoạt động được, và nó càng ngày càng xấu đi (không tạo quá nhiều mã này, tôi chỉ đưa nó vào hiển thị các hướng chung của những nỗ lực của tôi):

fn search_iter<'a,P>(data_path: P, city: &'a str) 
    -> Box<Iterator<Item=DataRow> + 'a> 
    where P: AsRef<Path> 
{ 
    struct ResultIter<'a> { 
     reader: csv::Reader<File>, 
     wrapped_iterator: Option<Box<Iterator<Item=DataRow> + 'a>> 
    } 

    impl<'a> Iterator for ResultIter<'a> { 
     type Item = DataRow; 

     fn next(&mut self) -> Option<DataRow> 
     { self.wrapped_iterator.unwrap().next() } 
    } 

    let file = File::open(data_path).expect("Opening file failed!"); 

    // Incrementally initialise 
    let mut result_iter = ResultIter { 
     reader: csv::Reader::from_reader(file).has_headers(true), 
     wrapped_iterator: None // Uninitialised 
    }; 
    result_iter.wrapped_iterator = 
     Some(Box::new(result_iter.reader 
           .decode() 
           .map(|row| row.expect("Failed decoding row")) 
           .filter(|&row: &DataRow| row.city == city))); 

    Box::new(result_iter) 
} 

This question dường như liên quan đến cùng một vấn đề, nhưng tác giả của câu trả lời giải quyết nó bằng cách làm cho các dữ liệu liên quan static, mà tôi không nghĩ là một sự thay thế cho điều này câu hỏi.

Tôi đang sử dụng Rust 1.10.0, phiên bản ổn định hiện tại từ gói Arch Linux rust.

+4

Tôi muốn cảm ơn bạn vì đã đặt câu hỏi độc đáo. Nhiều khách truy cập thường xuyên không thể hiện nhiều sự chuẩn bị, người hỏi ít lần đầu tiên. Thanh danh! – Shepmaster

+1

@Shepmaster Cảm ơn, tôi đã cố hết sức để viết một câu hỏi hay, và có vẻ như tôi đã có một câu trả lời đúng chất lượng cho nó! Tuy nhiên, cảm ơn cho chỉnh sửa phong cách của bạn. –

Trả lời

3

Đường dẫn thẳng để chuyển đổi chức năng ban đầu sẽ chỉ đơn giản là wrap the iterator. Tuy nhiên, làm như vậy trực tiếp sẽ dẫn đến các vấn đề vì you cannot return an object that refers to itself và kết quả của decode đề cập đến Reader. Nếu bạn có thể vượt qua điều đó, bạn cannot have an iterator return references to itself.

Một giải pháp là chỉ đơn giản là tái tạo DecodedRecords iterator cho mỗi cuộc gọi đến iterator mới của bạn:

fn search_iter<'a, P>(data_path: P, city: &'a str) -> MyIter<'a> 
    where P: AsRef<Path> 
{ 
    let file = File::open(data_path).expect("Opening file failed!"); 

    MyIter { 
     reader: csv::Reader::from_reader(file).has_headers(true), 
     city: city, 
    } 
} 

struct MyIter<'a> { 
    reader: csv::Reader<File>, 
    city: &'a str, 
} 

impl<'a> Iterator for MyIter<'a> { 
    type Item = DataRow; 

    fn next(&mut self) -> Option<Self::Item> { 
     let city = self.city; 

     self.reader.decode() 
      .map(|row| row.expect("Failed decoding row")) 
      .filter(|row: &DataRow| row.city == city) 
      .next() 
    } 
} 

Điều này có thể có chi phí liên kết với nó, tùy thuộc vào việc thực hiện các decode. Ngoài ra, điều này có thể "tua lại" về đầu của đầu vào - nếu bạn đã thay thế Vec thay vì csv::Reader, bạn sẽ thấy điều này. Tuy nhiên, nó sẽ xảy ra để làm việc trong trường hợp này. Ngoài ra, tôi thường mở tệp và tạo csv::Reader bên ngoài hàm và chuyển qua trình lặp vòng DecodedRecords và biến đổi nó, trả về bí danh newtype/box/type xung quanh trình lặp lặp cơ bản. Tôi thích điều này vì cấu trúc mã của bạn phản ánh thời gian sống của các đối tượng.

Tôi hơi ngạc nhiên khi không triển khai IntoIterator cho csv::Reader, điều này cũng sẽ giải quyết được sự cố vì sẽ không có bất kỳ tham chiếu nào.

+0

Cảm ơn bạn đã trả lời! Việc tái tạo trình lặp lại tấn công tôi là hơi đáng ngại, nhưng nó chắc chắn hoạt động và tôi cho rằng nó sẽ đẹp hơn nếu 'Trình phát hành' đã được thực hiện. Có vẻ như chúng tôi đã may mắn rằng 'Reader' đã không tua lại như bạn nói (tôi thấy điều này phản trực giác; tua lại là hành vi thông thường của các vòng lặp trong kinh nghiệm của tôi); nếu không chương trình có thể phải được tái cơ cấu. Tôi đã từng viết các hàm nhiều hay ít như các hộp đen phản chiếu mô hình tinh thần của tôi về các bước giải quyết vấn đề, nhưng có lẽ đây không phải là một sự trừu tượng "không chi phí" hợp lý trong Rust. –

Các vấn đề liên quan