15 ms·
Effective Rust (2021)
- boredumb 3y agoThanks! I've been writing quite a lot of rust lately but have been mostly silo'd to my own so i'll definitely read through and see how wildy un-idiomatic my code is. Even just scanning through it I saw `Avoid matching Option and Result` and the `if let` stuff that I have NOT been using and it looks much more concise already.
- bryanlarsen 3y agoPedantic clippy will suggest those changes, amongst others. I wouldn't recommend running pedantic clippy in CI, but it is useful to run it against your app occasionally.
- boredumb 3y agoVery cool, running it on the project I have open immediately started barfing at me over using 'return' in quite a few spots.
- weavie 3y agoThe example at the end of that section: pub fn encrypted(&self) -> Vec<u8> { encrypt(self.payload.as_ref().unwrap_or(&vec![])) } Needlessly allocates a `Vec` if `self.payload` is `Some`. Really you should do: encrypt(&self.payload.as_ref().unwrap_or_else(|| &vec![])) But that isn't going to work because the `else` Vec will be dropped at the end of the closure. In this situation (if the code was on a hot path where every bit of performance mattered) I would probably do something like: match self.payload.as_ref() { Some(payload) => encrypt(payload), None => encrypt(&vec![]) } There is a slight bit of duplicated code. I'd be curious if anyone could come up with a better way.
- tveita 3y agoFrom https://doc.rust-lang.org/std/vec/struct.Vec.html https://doc.rust-lang.org/std/vec/struct.Vec.html if you construct a Vec with capacity 0 via Vec::new, vec![], Vec::with_capacity(0), or by calling shrink_to_fit on an empty Vec, it will not allocate memory. So an empty vec![] is just a struct on the stack; very cheap to make, and easy for the compiler to optimize out if it can see that it's not used in some paths.
- deleted 3y ago[deleted]
- laeri 3y agoCan you also use `unwrap_or_default` in this example?
- Omin 3y agoNo, because it requires an &Vec<_> and that doesn't implement Default and for good reason. Just ask yourself, where would the default empty vec live so that you could create a reference to it. When using unwrap_or(&vec![]), it lives in the enclosing stack. Without the reference, you could use unwrap_or_default().
- laeri 3y agoThanks for your reply that cleared it up. Was a bit confused why the `unwrap_or(&vec![])` worked but as you said it lives in the enclosing stack.
- jpochyla 3y agoThis is... not how I would really write it. If `encrypt` takes a `&[u8]`, as it should, you don't need a `&vec![]` at all, and can do: self.payload.as_deref().unwrap_or_default() ...which I think is the best here.
- School-Cotton 3y ago> Needlessly allocates a `Vec` if `self.payload` is `Some`. Empty vecs don’t require a heap allocation, so your original code is actually fine. In release mode it should compile to exactly the same instructions as your last example.
- HankB99 3y agoI lack Rust experience to comment on your suggestion but it does make me wonder if the author has provided a means for feedback in order to make this "More Effective Rust" (No pun intended if you're familiar with Meyers' followup book.)
- weavie 3y agoThe other commenters to this are correct, `Vec::new()` doesn't actually allocate so the code in the book is fine. I had a brain fart. If that function was taking a reference to an object that was fairly expensive to create a default value for then my conundrum would still exist.
- masklinn 3y ago> Needlessly allocates a `Vec` if `self.payload` is `Some`. Vec is documented not to allocate if empty (unless created with a set capacity). So all the code does is store 3 u64 on the stack.
- keyle 3y agoThat is something I did naturally coming from Swift. There is a growing bunch of people I've met that said the match stuff is overused.
- harpiaharpyja 3y agoOccasionally matching Option or Result is actually the most readable way to express some logic. But it's definitely not the first thing you should reach for. When I first started using Rust I did make the mistake of matching Option and Result everywhere and when I learned better I did cut out quite a bit of verbosity and unnecessary nesting by going back and refactoring those in code I had written. But that doesn't mean you should never use match with them. I thought I should share this experience for anyone new to the language reading this.
- hresvelgr 3y agoThere seems to be a heavy emphasis on explaining high level Rust concepts using parallels in C++ which overall leads to a weaker explanation for most items. Perhaps this should be titled: Effective Rust coming from C++.
- FL33TW00D 3y agoI think that title would be too restrictive. The author dedicates a small portion of each section to highlighting analogous concepts in C++, but I don't think C++ -> Rust programmers are the only audience that benefit from this book.
- laeri 3y agoIt is quite helpful in my opinion and I don't come from a C++ background (although I have some experience with it).
- School-Cotton 3y agoIMO (and this is certainly not shared by everyone), Rust is best understood as being a better C++. That’s it. It’s the best language for when you want to use a mainstream language to write large systems in native code and can’t tolerate a garbage collector. Trying to shoehorn Rust into every niche is making the language lose focus.
- masklinn 3y agoRegardless of your opinion, defining Rust in terms of C++ is unhelpful to people who don’t know C++, or don’t know much of it. And having to learn C++ in order to learn Rust is not sane.
- avgcorrection 3y agoGit might be a good kind of advancement on Subversion but not everyone who learns Git will have used Subversion.
- robertlagrant 3y ago
- sheepscreek 3y agoAfter glancing through this, I can confidently say this is the “Rust, The Good Parts” I’ve been waiting for. I really appreciate the simple explanation for all “whys”. Most folks like me that are doing application programming don’t need Rust’s full feature set. You can benefit from type safety/performance/etc even with a subset of the language. And in doing so, you are greatly enhancing the readability and on-boarding experience for those new to Rust. This realization isn’t new, it was/is widely regarded as a best practice for C++ (readability over conciseness).
- fallat 3y ago> After glancing through this, I can confidently say Yeah ok.
- afavour 3y ago> Most folks like me that are doing application programming don’t need Rust’s full feature set. You can benefit from type safety/performance/etc even with a subset of the language. I do think Rust suffers sometimes from guides putting all the complication upfront. When I first started playing with Rust lifetimes confused me… so I used Rc<> everywhere I’d need to use a lifetime and everything worked great. Then, later, I got to grips with how lifetimes actually work. The vast majority of Rust applications aren’t so performance centric that a little reference counting would kill them. But you rarely see it recommended because it’s not the Rust Way.
- mwcampbell 3y agoHow does software get slow enough that it's one of our favorite things to complain about? There are multiple causes, of course, but I think one of them is death by a thousand cuts, that is, lots of little performance compromises that ostensibly don't matter. I don't always do everything in the optimal way, but I appreciate that Rust at least gives us the possibility of having safety without compromising speed, and I think it's good to push harder in that direction.
- harpiaharpyja 3y ago
- lexicality 3y agoImmediately put off by an egregious error in the Aggregate Types section in the very first chapter: > Unlike the bool version, if a library user were to accidentally flip the order of the arguments, the compiler would immediately complain: error[E0423]: expected function, found macro `print` --> use-types/src/main.rs:89:9 | 89 | print(Output::BlackAndWhite, Sides::Single); | ^^^^^ not a function | help: use `!` to invoke the macro | 89 | print!(Output::BlackAndWhite, Sides::Single); | + which is a compiler error for calling the wrong function, not swapping the arguments. Was this book not proof read at all? Is it AI generated? I'm not sure I can trust anything in it any more if something this basic was allowed through.
- xwowsersx 3y agoIt does indeed need to be proofread and that is annoying. It seems to me like the author just copy-pasted the completely wrong compiler output. Because in that section, he's trying to show how by having two distinct types for the arguments, the incorrect order would lead to a compiler error, as opposed to the case where both args were of type `bool` (and that this helps you avoid hard-to-spot mistakes at the point of invocation)
- TylerE 3y agoIf they'd used a sane document language like LaTeX instead of markdown, it would have been trivial to actually run the examples on build. Markdown has it's place... books is not it.
- cdirkx 3y agoI agree LaTeX has it's uses, but... markdown can also do this trivially. See for example the Rust Book [1], and `cargo test` automatically runs example code in markdown doccomments. [1] https://doc.rust-lang.org/stable/book/ch01-02-hello-world.html https://doc.rust-lang.org/stable/book/ch01-02-hello-world.ht...
- deleted 3y ago[deleted]
- softirq 3y agoAfter writing primarily no standard library C for 15 years, I have to say that I find Rust just as ugly and cumbersome as C++ (not debating its safety guarantees). It seems like languages that add sufficiently advanced type/macros systems always spiral into unwieldy beasts where you spend a bunch of your time arguing with the type systems and writing blog posts about some new piece of type theory to solve a problem you would have never have had with C. People just get greedier with deriving code and wanting more "magic" until every program only makes sense to its author. I don't think I will ever like kitchen sink languages. Experience has taught me that the most effective tool is the simplest one, which for most use cases today would be Go. For systems programming I just shudder to think how convoluted and hard to read things will become when we take already extremely complex code written in the simplest terms in C and port it to Rust.
- cumshitpiss 3y ago[dead]
- rascul 3y ago> I don't think I will ever like kitchen sink languages. What makes a kitchen sink language?
- matthewaveryusa 3y agoI would describe it as a language where there is more than one strong idiom For C and Go, there really is only one prevailing idiom on how to write code (for C one could argue that static vs. dynamic allocation are two idioms, and multiple ways of doing concurrency) For C++, whenever you have the chance to abstract some code into an interface you'll have the choice to create a template or use polymorphism, concurrency can now be done with an event loop, co-routines, threads... Rust has the same bug/feature where there's more than one idiomatic way of doing things.
- Capricorn2481 3y agoI feel like people have been saying this about Go for a while and I don't see how it's true. The only real idioms out of Go are explicit error handling, types implementing interfaces, and the formatter. Otherwise, you can kinda do whatever you want in Go, and I have yet to feel at home reading a Go codebase.
- vaylian 3y ago> Rust has a reputation for having a steep on-ramp I like how the author has avoided the common misunderstanding of the word "learning curve" by choosing a different word.
- gnuvince 3y agoI disagree with many of these: I've enough experience writing, reading, and maintaining Rust code to know that some of these sound good in theory, but are harmful in practice.
- loeg 3y agoFor example?
- gnuvince 3y ago> Avoid matching Option and Result It's nice to use the ? operator, but if you also want to include logging/metrics, I really recommend using `match` to make it clear what each branch does. > Embrace the newtype pattern A nice pattern for certain use cases, but often abused by programmers. Out of the box, a newtype is less useful than the type it wraps, because it has none of the underlying type's methods. This results in either having a ton of boiler-plate code that just does `self.0.underlying_method()` or in programmers making the wrapped value public, which defeats the purpose. "Embrace" to me means "use it when possible", but my experience says that it should be like spice, a little bit can improve the dish, but too much can ruin it. > Consider using iterator transforms instead of explicit loops Similar comment to the point about avoiding matches for Option and Result. For simple use cases, a .map() or .filter() can be good and easy to read, but I've also seen monstrosities with .skip(), .filter_map(), .chunks(), etc. that become a lot harder to decipher than the equivalent for-loop. We also tend to try and write those in an FP style with no side effects, and sometimes that can discourage people from including logs and metrics in places where they would be useful because they would tarnish the purity of the code. Whatsmore, in Rust closures are more finicky than in GC'ed languages like OCaml or F#, and some iterator methods take their closure parameters by ownership, others borrow, and it's very often an unnecessary mental burden. > Don't panic Good idea in general, but panicking is a great tool when the program is in a state that was not predicted by the programmer or cannot be recovered from. "In general, avoid panicking" would be better advice. > Minimize visibility It's been accepted since the advent of OOP that we should aim to minimize the visibility of the data in our structures. Every Java IDE defaults to making fields private and auto-generating getters and setters. But more often than not, when programmers hide all the internal details, they limit what other programmers. Yes, other programmers could use the API wrong and create bugs in their programs, but they could also use it in ways that the original programmer could not have anticipated and make something amazing out of it. And when other programmers complain that they are limited in what they can do, the answer is always "we'll add more stuff (traits, plugins, closure parameters, etc.) to help you do more", increasing the complexity of the code. > Listen to Clippy Some of the warnings from clippy are good (e.g., when it reminds you to use .unwrap_or_else() instead of .unwrap_or()), but a lot of the lints are opinions made into code, and people view you very suspiciously if you disable any of them, which drives the general style of Rust toward the style of the people who successfully argued for their opinions to be part of clippy. For example, I like putting return statements in my code; yes clippy, I know that the value of a block is the value of its last expression and thus it's unnecessary to put the return, but I want to make it more explicit that we are exiting the function here, why are you complaining about that?
- mrbonner 3y agoI jump in and find a section about borrow checker. At the end, this book explicitly recommends using smart pointer (RC and RefCell) to write your internal DS to pass them around instead of convoluted one and fight with the borrow checker. I never have seriously coded in Rust but this is what observed as well. I mean how much of an overhead if RC comparing to the real borrow checker coming from Java or JS world? What are your pro Rust dev opinions about the use of RC in the real world?
- MuffinFlavored 3y ago> (RC and RefCell) Rc is Reference Counting Arc is Atomic Reference Counting RefCell is Reference Cell I'm like a "medium at Rust" person and I very rarely use Box or RefCell or Rc. I wonder what I'm missing due to not really understanding them/when to use them. I don't really use lifetimes either. Maybe I'm not that "medium"...
- cmrdporcupine 3y agoMy personal opinion on these is, it's probably good you're not using them all over: use only when needed, and be clear if you really need them. Basically: Box is used rather frequently, it basically corresponds to unique_ptr in C++, and is basically a way of a) having something be a passable heap-allocated thing and b) is often used to hold dyn implementations of a trait. Excessive instances of Box<dyn .. in your code are often a sign or code smell that you're bringing patterns from object-oriented languages through polymorphism via traits into what's really a non-OO language. See also: the Effective Rust entry about preferring generics over traits. Note at one point Box actually had its own special syntax (sigil ~) this was dropped. I am not a Rust core expert, but to me this feels like maybe a mistake...? Rc is sort of the "out" for when the borrow checker is giving too many problems for a given access pattern and you want to share a piece of state more conveniently within one thread. It's not used very often, you'll see more uses of Arc. Arc is generally used for shared state across threads. It can be combined with Mutex to create safe shared mutable state. RefCell's use is basically pushing borrow checking into the runtime instead of at compilation. Its main use is for the so-called "interior mutability pattern": https://doc.rust-lang.org/book/ch15-05-interior-mutability.html https://doc.rust-lang.org/book/ch15-05-interior-mutability.h... -- basically allowing mutable borrow in an immutable context (e.g. immutable self reference). So really, you shouldn't be reaching for this often. This is more something that data structure library designers might use in a "trust me, I know what I'm doing" way, or for, say a use like incrementing a usage counter or something which you know isn't really mutating state and you want do it inside an immutable reference. Similar to some uses of const_cast in C++, I guess.
- svieira 3y agohttps://www.lurklurk.org/effective-rust/errors.html#minimal-errors https://www.lurklurk.org/effective-rust/errors.html#minimal-... has what seems (to my naive understanding) to be mis-statement. Given a trait: pub fn find_user(username: &str) -> Result<UserId, MyError> { let f = std::fs::File::open("/etc/passwd") .map_err(|e| format!("Failed to open password file: {:?}", e))?; // ... } and an error type: #[derive(Debug)] pub struct MyError(String); impl std::fmt::Display for MyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for MyError {} And an implementation of `From<String>` for `MyError`: impl std::convert::From<String> for MyError { fn from(msg: String) -> Self { Self(msg) } } The book asserts: > When it encounters the question mark operator (?), the compiler will automatically apply any relevant From trait implementations that are needed to reach the destination error return type. I thought that `?` only unwrapped `Option` and `Result` and was not overloadable. Is [the Rust doc on `?`][1] leaving something important out, or is this a mis-statement? [1]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator https://doc.rust-lang.org/reference/expressions/operator-exp...
- deleted 3y ago[deleted]
- ben-schaaf 3y ago> I thought that `?` only unwrapped `Option` and `Result` and was not overloadable. This is still true, although the experimental Try trait in nightly does provide this. `From` is applied to the Error of the returned Result, ie. Result<T, String> => Result<T, MyError>.
- masklinn 3y ago> This is still true Not since the addition of ControlFlow.
- masklinn 3y agoThe Rust doc is leaving things out, though whether it’s important is a complicated question. `?` works off off the Try trait (https://doc.rust-lang.org/std/ops/trait.Try.html https://doc.rust-lang.org/std/ops/trait.Try.html), which is unstable so you can’t currently implement it in userland (except on nightly), and it is implemented on more than Option and Result, specifically ControlFlow (https://doc.rust-lang.org/std/ops/enum.ControlFlow.html https://doc.rust-lang.org/std/ops/enum.ControlFlow.html). So whether it’s important to mention depends mostly on how important you think try_fold and try_for_each are, and if you think you’ll miss the information if ever you need those given that’s clearly mentioned in the ControlFlow docs.
- ojosilva 3y agoDoes anyone know how to convert these e-book web templates to EPUB? There are a hack lot of Rust books with these templates and it would be great to turn them into epub. There are a couple of FF addons for this but they are just meh.
- awestroke 3y agoI use the WebToEpub browser extension, it's great and worked very well on the Rustonomicon which uses the same template: https://github.com/dteviot/WebToEpub https://github.com/dteviot/WebToEpub Just make sure to unselect chapters that aren't part of the "book"
- idoubtit 3y agoFrom the HTML source of the page, this site was made with "mdBook". No link, but a web search finds the source. On the mdBook site, the official backends produce HTML or Markdown, but community backends offer more perspective. An experimental epub backend exists. https://github.com/rust-lang/mdBook/wiki/Third-party-plugins https://github.com/rust-lang/mdBook/wiki/Third-party-plugins A web search gives a public repository for this book, so it should be possible to convert the source of the book into an epub file. https://github.com/rust-lang/book/blob/main/first-edition/src/effective-rust.md https://github.com/rust-lang/book/blob/main/first-edition/sr...
- morning-coffee 3y agoI appreciate the author's efforts and amount of time that went into this. But, IMHO, there are some things that are just plain bad advice: e.g. https://www.lurklurk.org/effective-rust/panic.html https://www.lurklurk.org/effective-rust/panic.html "Another sensible use of panic!, even in library code, is in situations where it's very rare to encounter errors, and you don't want users to have to litter their code with .unwrap() calls." The litmus test for panic! vs. Result<T,E> is not rarity of occurrence, it's whether the condition represents a programming bug or a recoverable error. A good treatise on this topic here: https://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors https://joeduffyblog.com/2016/02/07/the-error-model/#bugs-ar...
- klysm 3y agoIf you are writing a library, whether or not .unwrap() is sensible is not up to you - it's up to the user.
- photonbeam 3y agoIs there a tool that can assert a library doesnt/cant panic?
- morning-coffee 3y agohttps://docs.rs/no-panic/latest/no_panic/ https://docs.rs/no-panic/latest/no_panic/ might be what you're looking for
- ajross 3y agoJust to jump in from the peanut gallery: this kind of discussion, about the "right" way to do things in a complicated language with lots of choice, prompting digressions about conventions and mildly-kludgey third party "soft" solutions to what would seem to be generic problem... ...is exactly where C++ was in 1995 when Meyers published Effective C++. The specifics of the argument notwithstanding, the very existence of this discussion validates the need for this book. And it also says some somewhat awkward things about the direction in which Rust is evolving.
- fortylove 3y agoThis isn't exactly on topic, but is anyone else experiencing Rust fatigue?
- arunc 3y agoWhat is Rust's answer for Structured Concurrency?
- bilekas 3y agoThe majority of this is not specific to Rust and not exactly groundbreaking stuff, certainly a good reference material for effective engineering.
- jmcomets 3y ago+1, the link to Joel Spolky's post on Unicode is probably the most interesting read I've found this year: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ https://www.joelonsoftware.com/2003/10/08/the-absolute-minim...
- chrismorgan 3y agoI was never much impressed with that article (given its title, I say—given its title) from the first time I saw it, probably around 2010, and it has aged poorly. Some of my complaints about it: • It tells a verbose story, rather than just telling you what you need to know succinctly. (Seriously, the 3,600-word article could be condensed to under a thousand words with no meaningful loss—and the parts that I consider useful could be better expressed in well under 500 words. As a reminder of where I’m coming from in this criticism: the title of the article is “The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”, so I expect it to be, y’know, absolute-minimumy.) • It spends way too much time on history that even in 2003 wasn’t particularly relevant to a lot of people (though yeah, if you were working in the Windows ecosystems he was working in, you would benefit from knowing about some of it), and which is now (twenty years later) completely irrelevant to almost everyone. • It portrays UCS-2 and UTF-16 as equivalent, which is disastrously wrong. (As in, “if you do that, you stand a good chance of destroying anything beyond U+FFFF”.) • As far as I can tell (I was a young child at the time), its chronology around UTF-8 and UCS-2/UTF-16 is… well, dubious at best, playing fast and loose with ordering and causality. • Really, the whole article looks at things from roughly the Windows API perspective, which, although what you’d expect from him (as a developer living in Microsoft ecosystems), just isn’t very balanced or relevant any more, since by now UTF-8 has more or less won. • It doesn’t talk about the different ways of looking at Unicode text: code units (mostly important because of the UTF-16 mess), scalar values, extended grapheme clusters, that kind of thing. A brief bit on the interactions with font shaping would also be rather useful; a little bidirectional text, a little Indic script, these days even a bit of emoji composition. These days especially, all of this stuff is far more useful than ancient/pre-Unicode history. • The stuff about browsers was right at the time, but that’s been fixed for I think over a decade now (browser makers agreeing on the precise algorithms to use in sniffing). (He’s absolutely right that Postel’s Law was a terrible idea.)