There is approximately such a piece of site parser code using select :

extern crate select; use select::document::Document; use select::predicate::{Class, Name, And}; fn main() { // Пример; реальность чуть сложнее, но не суть let html = "<main> <div class='vote-topic'> <div class='vote-item vote-count'> <span id='vote_total_blog_123'>+15.00</span> </div> </div> </main>"; let page = Document::from(html); let blog_id: u32 = page.find(And(Name("div"),Class("vote-item"))) .find(Name("span")).first() .unwrap().attr("id") .unwrap().split('_').collect::<Vec<_>>().last() .unwrap().parse::<u32>().unwrap(); println!("Blog id: {}", blog_id); } 

I tried to rewrite it so that at the output I get Option (which I will then transfer to Result; it’s not exactly interested in storing the exact cause of the error). The result works, but looks, to put it mildly, not very:

 let blog_id: Option<u32> = page.find(And(Name("div"),Class("vote-item"))).first() .and_then(|x| x.find(Name("span")).first()) .and_then(|x| x.attr("id").and_then(|x| Some(x.to_string()))) .and_then(|x| x.split('_').collect::<Vec<_>>().last().and_then(|x| Some(x.to_string()))) .and_then(|x| x.parse::<u32>().ok()); match blog_id { Some(i) => println!("Blog id: {}", i), None => println!("Cannot parse blog_id"), }; 

Is it possible to arrange this somehow more beautifully? If I try to simplify this somehow (especially the nasty to_string), the compiler immediately starts swearing for times of life, borrowing and other rubbish. The use of match or if let , I suspect, will lead to an overwhelming number of ladders, and if they are avoided, then the beauty will not increase much. I also tried to write a macro with a loop, equivalent to constant calls and_then , but it turned out just as ugly and to_string has not gone away.

Ideally, something like this Python equivalent:

 try: blog_id = int(page.find("div", {"class": "vote-item"}).\ find("span")[0].\ get("id").\ rsplit("_")[-1]) except Exception: blog_id = None 

(probably, it's better to just take xpath, but the question is not about that yet :)

4 answers 4

If as a result (in the return value of the function) you need to get a Result , to improve the readability of the code, you can use the try! . Any Option values ​​obtained from select operations can be converted to Result using ok_or method, with the error type of your choice, but such that it is converted ( Into / From traits) to the error type for Result returned by the function that contains your code with using try! .

 fn get_blog_id(page: &Document) -> Result<u32, MyDocError> { let item = try!(page.find(And(Name("div"),Class("vote-item"))).first() .ok_or(MyDocError::NoVoteItem))); let id = try!(item.attr("id") .ok_or(MyDocError::NoIdAttr))); ... } 

It is not clear to me what the problem is with the lifetime of borrowings in the chain and_then , but the approach outlined above will avoid this, since intermediate values ​​are tied to the slots at the time the containing function is called and AKA slices can be borrowed from them.

A possible disadvantage of this approach is that error values ​​in ok_or call ok_or are created anyway, so if the error type has nontrivial initialization and / or destructor (for example, creates a String ), it creates unnecessary work even in case of successful execution. To avoid this, you can use ok_or_else and hide the initialization of the error in a lambda expression, but this somewhat degrades the aesthetic quality of the code.

Added: macro libraries try_or and try_opt allow you to write an early return code Option or Result , similar to the one above, is more compact. In the latest versions of the language use the standard macro try! Can I replace it with an operator ? .

    Starting with Rust version 1.22, by the operator ? You can also use for quick return Option::None :

     fn get_blog_id(page: &Document) -> Option<u32> { let item = page.find(And(Name("div"), Class("vote-item"))).first()?; let id = item.attr("id")?; // ... } 

      Now we have somehow agreed on the use of the hado macro, which is similar to the mdo mentioned in the comments, but, according to some, a little bit better:

       hado! { el <- page.find(And(Name("div"),Class("vote-item"))).find(Name("span")).first(); id_s <- el.attr("id"); num_s <- id_s.split('_').last(); num_s.parse::<u32>().ok() } 

      In essence, it does the same thing as my code with and_then : for Some evaluates the following expression, but None leaves it as it is, and as a result, gives Option - but with a more beautiful syntax. (For some reason, there were no problems with the lifetime.) (Like mdo, there are numerous Monad::bind nested in each other, but so far no bad consequences have been noticed.)

        Hmm, say unwrap do not like. Let's see what we have.

        try! : I have long pretended one macro, called try! its essence is extremely simple. If there is an error, then return Result<..,..> to fn , if there is no error, return the object. Principle like unwrap .

         macro_rules! try { ($e:expr) => (match $e { Ok(val) => val, Err(err) => return Err(err), }); } 

        Description: https://doc.rust-lang.org/std/macro.try.html

        Code taken.

         fn write_to_file_using_try() -> Result<(), MyError> { //Result требуется обязательно реализовывать, так как вы используете try let mut file = try!(File::create("my_best_friends.txt")); //если все ок то верни обьект, если нет, заверши функцию и верни Result try!(file.write_all(b"This is a list of my best friends.")); //если все ок то все ок, если нет, заверши функцию и верни Result println!("I wrote to the file"); Ok(()) } 

        Also have an operator ? did not encounter work with him. Prefer using ? syntax to try!. ? is built in to the language and is more succinct than try!. It is the standard method for error propagation.

        unwrap I think you have already met him, his code is often described as

         fn unwrap(self) -> T { match self { Option::Some(val) => val, //если обьект относится к enum Option::Some(e) значит он не null и достань значение из него и верни мне Option::None => //если обьект относится к enum Option::None значит он относится к нулу и значения никакого мы не получили panic!("я упал"), } } 

        There are many more ways to describe, but they do not work for me (more unknown who implements them) http://m4rw3r.imtqy.com/rust-questionmark-operator

        Through IF (works through whatever you want, Option, Result, ...)

        Перепишу код примера на IF

         fn write_to_file_using_try() -> bool { if let Ok(mut file) = File::create("my_best_friends.txt") { if let Ok(_e) = file.write_all(b"This is a list of my best friends."){ println!("I wrote to the file"); return true; } } false } 

        Another IF example from my code

         pub fn save(&self) -> bool { { let lock = self.save_file.lock().unwrap(); if let Some(ref file_s) = *lock { if let Ok(file_f) = File::create(file_s){ let mut file = BufWriter::new(file_f); let end_w = [10u8; 1]; let locker = self.arr.lock().unwrap(); for i in locker.iter(){ file.write(i.as_bytes()).unwrap(); file.write(&end_w); } return true; } } } self.warning("no save file..."); false } 
        • try! and ? if an error occurs, the function will be interrupted, and this is not necessary in my case - andreymal
        • You can take advantage of a longer way if you don’t have to tear. but it will be longer, although it is clearly written. if let Ok (result) = something {} - Denis Kotlyarov