4 ms·
I haven't had a chance to work on a real-world problem with Rust (I mostly use Python, but have started learning Rust for fun), so this is genuine curiosity spe
by annnnd 10y ago
I haven't had a chance to work on a real-world problem with Rust (I mostly use Python, but have started learning Rust for fun), so this is genuine curiosity speaking: is this (https://github.com/ruma/ruma/blob/master/src/main.rs#L60 https://github.com/ruma/ruma/blob/master/src/main.rs#L60) idiomatic Rust? If I understand correctly, lines 63-76 are using chaining because Rust doesn't support optional / named parameters? And lines 78 - 107 are basically just "run server if no error"? Seems like an awful lot of code for what it does... Could it be make nicer or is such level of verbosity something normal in Rust?
EDIT: again, not trying to put anybody down, I would just like to learn if this code is representative of bigger Rust apps.
- MHordecki 10y agoIs it that verbose, though? I have a hard time seeing how it could be made any shorter in other languages, without sacrificing functionality. EDIT: I think what gives the code its length here is the explicit error handling. If you do the same in Python you'll end up with the same amount of code. In Rust, you could unwrap() everything and lose nice error messages and probably slash the LOC in half.
- reissbaker 10y agoWith the built-in error handling macro, you can generally do: let value = try!(fn_that_may_error()); Rather than having to muck around with explicit matching on Ok vs Err. The try! macro would make the linked function much less verbose... Except that what try! does is bubble the error further up the stack by returning an Err if the called function returns an Err — and since this is the main function, there's nowhere up the stack to go (main() has a return type of "Unit," which is Rust's version of void or null; for functions that pass errors up the stack you'd have a return type of Result<ReturnType, ErrorType>, which allows errors to be returned), so try! can't work. I agree that some of that error handling in 79-101 is pretty awkward looking, but luckily it's not super representative of idiomatic Rust: usually you can make Rust much more concise by using try!, with the exception of the main() function which can't use try! and has to actually handle the error.
- moosingin3space 10y agoI'd prefer to use `expect` in this case, or if I wanted to present a non-scary error message, write a custom macro to print the error and exit.
- Perceptes 10y agoYes, all of that is more or less idiomatic Rust. The first section of code you mention is using what's referred to as the "builder pattern" in Rust, where you have a dedicated type that chains method calls to mutably construct an object which is produced at the end. You're not alone in thinking this pattern is ugly and verbose (though I've grown to like it, personally.) There is a lot of discussion[1] about providing a terser approach via keyword arguments, but nothing concrete is planned at this point. The second section of code you mention is a match statement (essentially a fancy, type-based switch statement) that inspects the arguments presented to Ruma from the command line and dispatches to the appropriate code. This supports two subcommands currently, `run` and `secret`, which are the two arms of the match. The `run` arm loads the user's configuration from the configuration file into a Rust structure, then starts the web server, which blocks the process until it is killed, reporting any errors that occurred along the way. Rust is very explicit about code paths that are optional or may produce errors, so you often see the "some" and "none" (or "ok" and "error") cases handled explictly in code, which will certainly look verbose coming from languages like Python. I came from Ruby, myself, and what I've learned is that the places where Rust feels weighty are almost always making me accountable in places where I would've otherwise left a corner case unhandled or introduced a bug in a Ruby program. There are some constructs in the language to make handling optional and error cases a little less verbose (e.g. `unwrap_or`, `try!`, etc.) but in many cases I prefer the longer form for simplicity. [1] https://github.com/rust-lang/rfcs/issues/323 https://github.com/rust-lang/rfcs/issues/323
- scottlamb 10y agoLines 63-76 are the builder pattern. I think it's the standard idiom: https://aturon.github.io/ownership/builders.html https://aturon.github.io/ownership/builders.html which is familiar from C++ or Java. Lines 78-107 are "do stuff or handle error". You could do "do stuff or crash" instead more tersely via thing_that_might_fail.unwrap(). I think lines 80-87 for example are idiomatic. Lines 89-100 seem a little strange to me in that they embed one error case in an ok case. I would probably do one error per block, returning the server and then using it in a separate block. But I'm new to Rust so don't take my word as gospel. In any case, I think it's true in any language that when you explicitly handle every error in a unique way, your code is longer. I suppose one way to shorten would be to make each error-handling case more similar: just print the error without a unique prefix. Then make a function for each subcommand that returns Result<(), Error> (nothing on success, error on failure). Then the functions could use the try! macro to make the error-handling implicit (the macro automatically propagates error to the caller), and main() could handle all the errors with just one path. Something like: fn run() -> Result<(), Error> { let config = try!(Config::from_file()); let server = try!(Server::new(&config)); server.run() } fn secret() -> Result<(), Error> { let key = try!(generate_macaroon_secret_key()); println!("{}", key); Ok(()) } fn main() { ... let result = match matches.subcommand() { ("run", Some(_)) => run(), ("secret", Some(_)) => secret(), _ => Err(SomeErrorType::new("no such subcommand")), }; if let Err(e) = result { println!("error: {}", e); // or to stderr via the ugly: // writeln!(&mut stderr, "error: {}", e).unwrap(); process::exit(1); } } EDIT: I suppose you could also make some variation of the try! macro which takes another string to prepend to the error. (Calling or_else with a function to prepend, maybe.) Then you can duplicate the original error messages in this slightly more terse form.