2015-09-28 5 views
1

У меня был большой блок кода, который открывает файлы и ищет содержимое по строкам, а затем делает что-то в каждой соответствующей строке. Я хочу включить это в свою собственную функцию, которая берет путь к файлу и дает соответствующие строки, но я не могу понять, как правильно это учесть.Как получить функцию для возврата Vec-подобных строк?

Вот что я думаю, что это близко, но я получаю ошибку компиляции:

/// get matching lines from a path 
fn matching_lines(p: PathBuf, pattern: &Regex) -> Vec<String> { 
    let mut buffer = String::new(); 
    // TODO: maybe move this side effect out, hand it a 
    //  stream of lines or otherwise opened file 
    let mut f = File::open(&p).unwrap(); 
    match f.read_to_string(&mut buffer) { 
     Ok(yay_read) => yay_read, 
     Err(_) => 0, 
    }; 
    let m_lines: Vec<String> = buffer.lines() 
     .filter(|&x| pattern.is_match(x)).collect(); 
    return m_lines; 
} 

и ошибка компилятора:

src/main.rs:109:43: 109:52 error: the trait `core::iter::FromIterator<&str>` is not implemented for the type `collections::vec::Vec<collections::string::String>` [E0277] 
src/main.rs:109   .filter(|&x| pattern.is_match(x)).collect(); 
                  ^~~~~~~~~ 
src/main.rs:109:43: 109:52 help: run `rustc --explain E0277` to see a detailed explanation 
src/main.rs:109:43: 109:52 note: a collection of type `collections::vec::Vec<collections::string::String>` cannot be built from an iterator over elements of type `&str` 
src/main.rs:109   .filter(|&x| pattern.is_match(x)).collect(); 
                  ^~~~~~~~~ 
error: aborting due to previous error 

Если я использую String вместо &str вместо этого я получаю эту ошибку:

src/main.rs:108:30: 108:36 error: `buffer` does not live long enough 
src/main.rs:108  let m_lines: Vec<&str> = buffer.lines() 
              ^~~~~~ 

Какой смысл имеет смысл. Я предполагаю, что строки остаются внутри buffer, который выходит за пределы области действия в конце функции, поэтому сбор вектора ссылок на строки нам действительно не помогает.

Как вернуть набор строк?

ответ

4

Давайте начнем с этой версией, которая работает на Rust Playground (это хорошая идея, чтобы сделать MCVE, задавая вопрос):

use std::path::PathBuf; 
use std::fs::File; 
use std::io::Read; 

fn matching_lines(p: PathBuf, pattern: &str) -> Vec<String> { 
    let mut buffer = String::new(); 
    let mut f = File::open(&p).unwrap(); 
    match f.read_to_string(&mut buffer) { 
     Ok(yay_read) => yay_read, 
     Err(_) => 0, 
    }; 
    let m_lines: Vec<String> = buffer.lines() 
     .filter(|&x| x.contains(pattern)).collect(); 
    return m_lines; 
} 

fn main() { 
    let path = PathBuf::from("/etc/hosts"); 
    let lines = matching_lines(path, "local");  
} 

Давайте посмотрим на подписи для str::lines:

fn lines(&self) -> Lines // with lifetime elision 
fn lines<'a>(&'a self) -> Lines<'a> // without 

Я сначала показал, как он выглядит в источнике, и что вы можете мысленно перевести на второй. Он вернет итератор строковых фрагментов, которые поддерживаются String, которые вы прочитали. Это хорошая вещь, так как она очень эффективна, так как требуется только одно распределение. Однако вы не можете return an owned value and a reference to that value at the same time. Проще всего сделать, это конвертировать каждый из линий в принадлежащей строку, как Benjamin Lindley предлагает:

let m_lines: Vec<String> = 
    buffer 
    .lines() 
    .filter(|&x| x.contains(pattern)) 
    .map(ToOwned::to_owned) 
    .collect(); 

, который получает свой код для компиляции, но он все еще может быть лучше. Ваше match заявление может быть заменен unwrap_or, но так как вы полностью игнорируя регистр ошибок, вы можете также просто использовать _:

let _ = f.read_to_string(&mut buffer); 

Обратите внимание, что это действительно не хорошей идеей. Ошибки важны для сообщения, и отбрасывание на ошибку укусит вас, когда вам нужно, чтобы о нем сообщалось больше всего! Вероятно, безопаснее использовать unwrap и позволить вашей программе умереть при возникновении ошибки.

Далее не следует использовать явные операторы return и не предоставлять аннотации типов, если вам не нужно.Так как ваша функция возвращает Vec<String>, вы можете заменить последние две строки с просто:

buffer 
    .lines() 
    .filter(|&x| x.contains(pattern)) 
    .map(ToOwned::to_owned) 
    .collect() 

Вы также могли бы быть более открытыми о типах вы принимаете для p, чтобы лучше соответствовать тому, что File::open поддерживает:

fn matching_lines<P>(p: P, pattern: &str) -> Vec<String> 
    where P: AsRef<Path> 

Все вместе:

use std::path::{Path, PathBuf}; 
use std::fs::File; 
use std::io::Read; 

fn matching_lines<P>(p: P, pattern: &str) -> Vec<String> 
    where P: AsRef<Path> 
{ 
    let mut buffer = String::new(); 
    let mut f = File::open(p).unwrap(); 
    let _ = f.read_to_string(&mut buffer); 

    buffer 
     .lines() 
     .filter(|&x| x.contains(pattern)) 
     .map(ToOwned::to_owned) 
     .collect() 
} 

fn main() { 
    let path = PathBuf::from("/etc/hosts"); 
    let lines = matching_lines(path, "local"); 
    println!("{:?}", lines); 
} 
+0

Я очень сильно предпочитаю разворачивать, чтобы _, после немых ошибок я никогда не думал, что может случиться укусил меня, когда они сделали. –

+0

@SteveKlabnik совершенно! Я просто очищал код, чтобы быть яснее, что ошибка игнорируется. Я немного подкорректирую текст, чтобы указать, что это определенно не очень хорошая идея. – Shepmaster

+0

Есть ли способ, которым я могу просто вернуть 'Lines' вместо' Vec '? Я продолжаю получать «неправильное количество параметров жизни» –

4

Вы можете преобразовать свои струнные срезы в принадлежащие String объекты с помощью функции map.

let m_lines: Vec<String> = buffer.lines() 
     .filter(|&x| pattern.is_match(x)) 
     .map(|x| x.to_owned()) 
     .collect(); 

Затем вы должны быть в состоянии вернуться m_lines из функции.

 Смежные вопросы

  • Нет связанных вопросов^_^