2017-08-24 63 views
-2

所以我們的目標是編寫一個獲取兩條路徑的函數,input_diroutput_dir,並將所有降價文件從input_dir轉換爲output_dir中的html文件。我仍然需要把頭繞在Rust身上。我怎樣才能讓這段代碼不再重複?

我終於設法讓它運行,但它非常令人沮喪。應該很難的部分非常容易:從Markdown到HTML的實際轉換實際上只有一行。看似簡單的部分是我花了最長的時間。使用路徑矢量並將所有文件放入它中,我用glob箱替換了它。不是因爲我無法工作,而是一團糟if letunwrap。一個簡單的函數遍歷元素列表並計算出哪些實際上是文件而不是目錄?要麼我需要四個縮進級別,如果if let或者我比match更糟糕。

我在做什麼錯?

但是讓我們先從一些事情,我試圖讓項目列表中過濾目錄僅包含實際的文件:

use std::fs; 
use std::vec::Vec; 


fn list_files (path: &str) -> Result<Vec<&str>, &str> { 
    if let Ok(dir_list) = fs::read_dir(path) { 
     Ok(dir_list.filter_map(|e| { 
      match e { 
       Ok(entry) => match entry.file_type() { 
        Ok(_) => entry.file_name().to_str(), 
        _ => None 
       }, 
       _ => None 
      } 
     }).collect()) 
    } else { 
     Err("nope") 
    } 
} 


fn main() { 
    let files = list_files("testdir"); 
    println!("{:?}", files.unwrap_or(Vec::new())); 
} 

所以,這個代碼不建,因爲行的文件名10活得不夠長。我想我可以創建一個擁有的String,但是會引入另一個嵌套層次,因爲OsStr.to_string()返回Result

現在我通過glob箱的代碼看,他們只是用一個可變矢量:

fn list_files (path: &str) -> Result<Vec<&str>, &str> { 
    let mut list = Vec::new(); 

    if let Ok(dir_list) = fs::read_dir(path) { 
     for entry in dir_list { 
      if let Ok(entry) = entry { 
       if let Ok(file_type) = entry.file_type() { 
        if file_type.is_file() { 
         if let Some(name) = entry.file_name().to_str() { 
          list.push(name) 
         } 
        } 
       } 
      } 
     } 

     Ok(list) 
    } else { 
     Err("nope") 
    } 
} 

這不僅增加了瘋狂的築巢,它也失敗,出現同樣的問題。如果我從Vec<&str>更改爲Vec<String>,它的工作原理:

fn list_files (path: &str) -> Result<Vec<String>, &str> { 
    let mut list = Vec::new(); 

    if let Ok(dir_list) = fs::read_dir(path) { 
     for entry in dir_list { 
      if let Ok(entry) = entry { 
       if let Ok(file_type) = entry.file_type() { 
        if file_type.is_file() { 
         if let Ok(name) = entry.file_name().into_string() { 
          list.push(name) 
         } 
        } 
       } 
      } 
     } 

     Ok(list) 
    } else { 
     Err("nope") 
    } 
} 

看起來我應該應用到我的第一次嘗試,對吧?

fn list_files (path: &str) -> Result<Vec<String>, &str> { 
    if let Ok(dir_list) = fs::read_dir(path) { 
     Ok(dir_list.filter_map(|e| { 
      match e { 
       Ok(entry) => match entry.file_type() { 
        Ok(_) => Some(entry.file_name().into_string().ok()), 
        _ => None 
       }, 
       _ => None 
      } 
     }).collect()) 
    } else { 
     Err("nope") 
    } 
} 

至少有點短...但它不能編譯,因爲std::vec::Vec<std::string::String>類型的集合不能從一個迭代過std::option::Option<std::string::String>類型的元素構建的。

在這裏很難保持耐心。爲什麼.filter_map返回Option而不是僅僅使用它們來過濾?現在我必須將第15行從}).collect())更改爲}).map(|e| e.unwrap()).collect()),它在結果集上重複一次。

這不可能是正確的!我在這裏錯過什麼?

+0

'ok()'返回'Option',然後將其包裝到'Some'中。你最終選擇'選項>'。從'Some(entry.file_name()。into_string()。ok())'中刪除'Some(...)''。這不是一個完整的答案,但至少它可以讓你開始。 – red75prime

+0

如果你的代碼有效(我發現很難從閱讀你的問題中得知),那麼要求更好地寫出它的方法是[更適合代碼評審](https://codereview.meta.stackexchange.com/q/ 32521分之5777)。 – Shepmaster

+0

謝謝@Shepmaster,但我的問題並沒有太多關於這段代碼,我只是作爲一個例子寫的。相反,我想看看我的一般問題是什麼導致此代碼如此瘋狂嵌套。 – koehr

回答

1

可以大量依靠? operator

use std::fs; 
use std::io::{Error, ErrorKind}; 

fn list_files(path: &str) -> Result<Vec<String>, Error> { 
    let mut list = Vec::new(); 

    for entry in fs::read_dir(path)? { 
     let entry = entry?; 
     if entry.file_type()?.is_file() { 
      list.push(entry.file_name().into_string().map_err(|_| { 
       Error::new(ErrorKind::InvalidData, "Cannot convert file name") 
      })?) 
     } 
    } 

    Ok(list) 
} 

不要忘記,你可以分割你的代碼的功能或實現自己的trait s到簡化的最終代碼:

use std::fs; 
use std::io::{Error, ErrorKind}; 

trait CustomGetFileName { 
    fn get_file_name(self) -> Result<String, Error>; 
} 

impl CustomGetFileName for std::fs::DirEntry { 
    fn get_file_name(self) -> Result<String, Error> { 
     Ok(self.file_name().into_string().map_err(|_| 
      Error::new(ErrorKind::InvalidData, "Cannot convert file name") 
     )?) 
    } 
} 

fn list_files(path: &str) -> Result<Vec<String>, Error> { 
    let mut list = Vec::new(); 

    for entry in fs::read_dir(path)? { 
     let entry = entry?; 
     if entry.file_type()?.is_file() { 
      list.push(entry.get_file_name()?) 
     } 
    } 

    Ok(list) 
} 
+0

非常感謝@Boiethios!我早些時候看到那個操作員,但每次我想使用它,它都不適合我。 – koehr

+0

也許因爲它只在夜間版本,之前。 – Boiethios

+0

你能指點我給那個操作員的一些文檔嗎? – koehr

1

替代用迭代器回答,playground

use std::fs; 
use std::error::Error; 
use std::path::PathBuf; 

fn list_files(path: &str) -> Result<Vec<PathBuf>, Box<Error>> { 
    let x = fs::read_dir(path)? 
     .filter_map(|e| e.ok()) 
     .filter(|e| e.metadata().is_ok()) 
     .filter(|e| e.metadata().unwrap().is_file()) 
     .map(|e| e.path()) 
     .collect(); 

    Ok(x) 
} 

fn main() { 
    let path = "."; 
    for res in list_files(path).unwrap() { 
     println!("{:#?}", res); 
    } 
} 
+0

謝謝! Rust編譯器在優化這些方面有多優秀?因爲我不想一遍又一遍地迭代。 – koehr

+0

這就是迭代器的美妙之處,這只是一個迭代,因爲只有一個調用要收集 – user25064

+2

這個答案很棒!但是,與OP的代碼有所不同,因爲在您的代碼中,一開始就不會返回錯誤。你只是默默地拋棄它們。 – Boiethios

相關問題