[HN Gopher] Effective Rust (2021)
       ___________________________________________________________________
        
       Effective Rust (2021)
        
       Author : FL33TW00D
       Score  : 363 points
       Date   : 2023-06-15 10:29 UTC (12 hours ago)
        
 (HTM) web link (www.lurklurk.org)
 (TXT) w3m dump (www.lurklurk.org)
        
       | morning-coffee wrote:
       | I 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
       | "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-ar...
        
         | klysm wrote:
         | If you are writing a library, whether or not .unwrap() is
         | sensible is not up to you - it's up to the user.
        
           | photonbeam wrote:
           | Is there a tool that can assert a library doesnt/cant panic?
        
             | morning-coffee wrote:
             | https://docs.rs/no-panic/latest/no_panic/ might be what
             | you're looking for
        
               | ajross wrote:
               | Just 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.
        
               | sophacles wrote:
               | This discussion exists for any programming language that
               | qualifies for the label "useful for 1 or more tasks".
        
               | ajross wrote:
               | Not at this level of pontification. Python and C and Java
               | and .NET have achieved much success without this kind of
               | "the community can't decide on the right way to do this
               | very fundamental thing[1]" kind of disconnect, largely by
               | scoping themselves such that the tradeoffs are made
               | internally to the language runtime and not a subject for
               | debate.
               | 
               | And, fine, Rust is more ambitious, just like C++ was. But
               | that too has tradeoffs in language complexity and
               | evolutionary cruft, something that is often cited as an
               | _advantage_ vs. C++. But as we 're seeing here, it's not
               | really. It's just that C++ is a few decades farther along
               | on the same curve.
               | 
               | [1] In this case, literally, how to handle a runtime
               | error.
        
               | Joker_vD wrote:
               | So, what's actually the correct answer? Or should we go
               | and read the whole book?
        
               | ajross wrote:
               | The joke is that there is no correct answer, and there
               | "should" be, which is why Rust needs books now. Things
               | always get worse and not better over time in this space.
        
               | shrimp_emoji wrote:
               | Instead of old and wise, languages only get old and
               | senile. :D (For which there is but one cure: breaking
               | backwards compatibility.)
        
           | burntsushi wrote:
           | This is wrong. Plenty of Rust libraries, including std,
           | correctly use unwrap: https://blog.burntsushi.net/unwrap/
        
         | cornstalks wrote:
         | You have a point but so does the OP. The classic example that
         | follows the OP's recommendation is allocation failures. Of
         | course, not everyone is happy that String or Vec will panic on
         | allocation failure and wish allocating methods returned a
         | Result instead. But changing all allocating APIs to returning a
         | Result would make others unhappy given how rarely a program can
         | truly recover from an allocation failure.
        
           | pornel wrote:
           | BTW, overcommit/oomkiller aside, recovery from allocation
           | failure in Rust can be easy and reliable.
           | 
           | There is an assumption carried over from C that code paths
           | handling OOM errors are untested and likely broken. However,
           | Rust doesn't have manually-written error handling paths like
           | that. It has `Drop` which is always correctly inserted by the
           | compiler, and regularly tested on normal function exits.
        
             | ridiculous_fish wrote:
             | Rust does have manually-written error handling paths like
             | that. See for example the panic handling in Vec.retain_mut:
             | https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#1577
             | 
             | I doubt most crates handle these scenarios correctly.
        
         | jdmichal wrote:
         | > 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.
         | 
         | Because that exact same conceptual separation worked so well
         | for Java with checked vs unchecked exceptions...
         | 
         | Everyone just started using unchecked exceptions because they
         | provide an easier coding experience.
         | 
         | Also, there's legitimate situations where the _library_ cannot
         | make that decision, but rather the user. As someone else says
         | in another example, who are you to say that my request to
         | allocate too much memory is a panicable offense? What if my
         | program can, in fact, recover from that situation?
         | 
         | At least in Java, catching checked vs unchecked exceptions is
         | the same if you do choose to handle them.
        
           | dllthomas wrote:
           | > Everyone just started using unchecked exceptions because
           | they provide an easier coding experience.
           | 
           | I think that's true, but I think there's some ambiguity about
           | what sort of "easier coding experience" mattered. I feel like
           | it's often portrayed as "too lazy to write the annotations",
           | but in practice the language to describe exceptions is often
           | too limited to express what we'd want. As a trivial example,
           | we'd like to be able to say "map() can throw anything its
           | argument can throw". That we can't say that means a
           | programmer working with checked exceptions needs to either
           | catch and suppress things they shouldn't be suppressing in
           | that function passed to map(), or build clever (and possibly
           | inefficient) workarounds to extract the exception(s?!?)
           | anyway. All of that has drawbacks enough that the downsides
           | of unchecked exceptions may be the correct choice, not merely
           | the lazy choice.
        
           | lumost wrote:
           | aye - in Java, this inevitably leads to strange catch
           | Exception/RuntimeException statements all over the code. In
           | practice, there are very few/no reasons to order a program
           | crash. Programming errors are inevitable on a large code
           | base, we learned the hardway in kernel development that Blue
           | Screen Of Death is not the correct outcome for an error.
        
           | rovolo wrote:
           | > Java with checked vs unchecked exceptions
           | 
           | Java doesn't make it nice to deal with exceptions though.
           | Converting a checked exception into an unchecked exception is
           | incredibly wordy:                   int output;         try {
           | output = doSomething();         } catch (IOException io) {
           | throw new RuntimeException(io);         }
           | 
           | vs Rust's method of defaulting an error result to a panic:
           | let output = doSomething().unwrap()
           | 
           | https://doc.rust-lang.org/std/result/
        
           | spion wrote:
           | Its very different from Java.
           | 
           | In Java and many other languages, the expectations are
           | declared at the callee site, which is a completely wrong
           | place to do it. Only the caller knows whether the situation
           | is expected or not.
           | 
           | Lets say I'm trying to get the element at index 20 of some
           | array-like datastructure.
           | 
           | Lets say that returns a `Result<T, E>`.
           | 
           | Lets add another twist: I've already checked whether the
           | length is > 30 for other resons in that same block.
           | Therefore, I know (if borrowing mechanisms ensure the absence
           | of concurrency issues) that getting the 20th element will not
           | cause an error. In this case I can use                 my_obj
           | .element_at(20)         .expect("Unexpected missing element
           | at index 20 even though number of elements is > 30")
           | 
           | As a caller, something is very wrong in this situation. If
           | so, I can make the choice (doesn't work for all types of
           | programs) to abort immediately.
           | 
           | edit: see other comments in the thread for a more believable
           | quicksort example, where you are getting the element at
           | `len/2` as pivot.
           | 
           | This is a somewhat certain situation. You can also use this
           | in softer situations, like e.g. a program that just can't run
           | if its data file (large language model files, for example)
           | cannot be found in `$PWD` because say, the docker image is
           | always built in a way that includes it and you wrote it to
           | only run within that image. As a caller, its up to you to
           | decide whether this situation is expected / recoverable or
           | not. (Yes you might criticize that rigid choice, but if the
           | choice is correct, so is the behavior to abort if the
           | condition is not satisfied)
           | 
           | You can also decide that the callee doesn't know whether the
           | error is expected and "propagate" the error further by
           | ensuring the function returns a Result<T, E> using the `?`
           | operator.
           | 
           | Rust is one of the rare languages that gets this right.
           | (Swift does too [1] - i am not quite aware of others)
           | 
           | [1]: https://docs.swift.org/swift-book/documentation/the-
           | swift-pr...
        
           | erik_seaberg wrote:
           | Checked exceptions aren't really compatible with generics.
           | You can't pass a lambda and have the compiler infer its
           | exception list now applies to the caller.
        
           | dap wrote:
           | > > 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. > Because that exact
           | same conceptual separation worked so well for Java with
           | checked vs unchecked exceptions... > Everyone just started
           | using unchecked exceptions because they provide an easier
           | coding experience.
           | 
           | Indeed. That's a problem with exceptions and checked
           | exceptions and the choices in Java around that, not the idea
           | that operational and programming errors might be handled
           | differently.
           | 
           | > Also, there's legitimate situations where the library
           | cannot make that decision, but rather the user.
           | 
           | What you're describing is that different components may
           | choose to treat these separately. That's fine -- there
           | doesn't need to be an objective answer. You can have one
           | library say "allocating too much memory is not an error we
           | care to deal with and we treat that as a programmer error".
           | And people using that can be told that's a limitation. That's
           | fine. All components have limitations. Another library that
           | implements the same thing can say "we treat allocating too
           | much memory as a recoverable operational error and express it
           | in this way". It's all tradeoffs.
           | 
           | That also doesn't mean _all_ such cases are subjective.
           | Another comment mentioned an index out of bounds in
           | quicksort. That's completely within the control of the
           | implementation and in most contexts there's no reason that
           | should ever be a recoverable error.
        
           | pkolaczk wrote:
           | > Everyone just started using unchecked exceptions because
           | they provide an easier coding experience.
           | 
           | This is because checked exceptions, contrary to Result, are
           | much more limited/annoying e.g. they don't compose well with
           | lambdas, can't be generic, and don't offer those nice
           | functional combinators.
        
           | hota_mazi wrote:
           | > Everyone just started using unchecked exceptions because
           | they provide an easier coding experience.
           | 
           | Yes, in much the same way that no longer checking for error
           | codes provides an easier coding experience.
           | 
           | But it makes your code more crashy.
        
         | lumb63 wrote:
         | IMO (systems programming background), the litmus test for
         | panic! vs. Result doesn't exist. Don't panic.
        
           | justinpombrio wrote:
           | Say you're implementing quick-sort, and you're checking the
           | pivot value like this:                   if (array.len() >=
           | 2) {             let pivot = array[array.len() / 2];
           | ...         }
           | 
           | Those brackets could panic, if the index was out of bounds!
           | Of course you can tell locally from the code that it can't be
           | out of bounds. But if you're _really_ not allowed to have
           | panics (and equivalently array accesses with square brackets,
           | as those can panic), then you have to write this code
           | instead:                   if (array.len() >= 2) {
           | let pivot = match array.get(array.len() / 2) {
           | Some(pivot) => pivot,                 None => return
           | Err(InternalQuicksortError("bad index somehow")),
           | };             ...         }
           | 
           | You do this all over your codebase and it gets hard to read
           | very quickly!
           | 
           | EDIT: clarified code
        
             | csnover wrote:
             | Well all you have to do then is make it not hard to read
             | :-)                  #[derive(thiserror::Error, Debug)]
             | enum Error {          #[error("bad index somehow")]
             | BadIndex        }             // ...             let pivot
             | = array.get(array.len() / 2).ok_or(Error::BadIndex)?;
        
               | morning-coffee wrote:
               | How does anyone on the stack above handle this error?
               | i.e. what can be done programmatically to deal with it?
               | 
               | I claim this is far worse than panic in this case...
               | you've now introduced a code path variant that folks
               | above think is "legitimate" or caused by some input or
               | something in the environment that could be changed to
               | make it go away... but that's not the case here... the
               | only thing that can be fixed is the code in question to
               | not be in this region if the array length is 2 or less.
               | It's a plain old "bug" and the only cure (other than more
               | cowbell) is to modify source and recompile.
        
               | phamilton wrote:
               | > How does anyone on the stack above handle this error?
               | For a web application, you respond to the request with
               | 5xx error.
               | 
               | Applications do many things. A degraded state (e.g. a
               | single endpoint being broken) is often preferable to a
               | full crash. It's very little effort to opt in to panics
               | by calling unwrap(). It's a lot of effort to recover from
               | panics. Let me, as the caller, make that decision.
        
               | ramranch wrote:
               | But any standard web framework already would use a panic
               | handler to turn a panic within application code to a 5xx
               | error.
        
               | throwway3423 wrote:
               | [flagged]
        
               | phamilton wrote:
               | Here's a discussion on actix-web and panic handlers:
               | https://github.com/actix/actix-web/issues/1501
               | 
               | Some technical limits, a lot of cultural opposition.
               | While it is possible to have a panic handler, it's
               | generally viewed as a bad idea. Not everything is
               | UnwindSafe, there are limits to how much a panic handler
               | can do, concerns about memory leaks.
               | 
               | Again, the caller can turn it into a panic easily enough
               | if that's what they want. leave the decision in their
               | hands.
        
             | Joker_vD wrote:
             | If only there was some way to propagate the range checks...
             | let lo, hi = array.bounds()         match lo < hi {
             | None => array,             Some(lo, hi) => {
             | let pivot_index = lo.avg_with(hi)  // guaranteed to be in
             | bounds                let pivot = array[pivot_index]     //
             | never panics, but the index type is opaque, not usize
             | ...rest of the qsort here             }         }
        
               | jedbrown wrote:
               | This is possible using branded indices.
               | 
               | https://plv.mpi-sws.org/rustbelt/ghostcell/paper.pdf
               | https://gitlab.mpi-
               | sws.org/FP/ghostcell/-/blob/master/ghostc...
        
               | Joker_vD wrote:
               | Yes, indeed. And it's not even a novel idea, my example
               | is lifted straight from one of the references of the
               | paper you've linked (namely, Kiselyov & Shan,
               | "Lightweight Static Capabilities", 2007).
               | 
               | I personally think that taking an optimization (e.g.
               | boundary checks elimination) and bringing it -- or more
               | precisely, the logic that verifies that this optimization
               | is safe -- to the source language-level is a very
               | promising direction of research.
        
             | dangerlibrary wrote:
             | Maybe my mind has been poisoned by a few years of Scala,
             | but that just looks like normal code to me. Bundle up those
             | errors and return a useful message to the user about why it
             | failed, call it a day.
        
               | defen wrote:
               | The point is that the language has decided that some
               | kinds of builtin operations can panic, in the interest of
               | developer ergonomics. The alternative is that every
               | indexing operation returns a Result, or you write 4 lines
               | of code instead of 1 for every indexing operation. So the
               | notion that the _programmer_ should never write  "panic!"
               | in their code does not fully address the underlying
               | problem, if the reason for not writing "panic!" is
               | "library code should never panic".
        
               | justinpombrio wrote:
               | The point is that the code _literally cannot panic under
               | any circumstances_. Just because there 's a call to
               | `unwrap()` or `[]` doesn't mean it's actually possible to
               | trigger that panic. I made that more explicit the code,
               | in case that wasn't clear.
               | 
               | If there's any question as to whether a line might
               | _actually_ panic or not, then absolutely, generate
               | helpful error messages. But when you can tell through
               | simple local reasoning that the panic will never happen,
               | you can just use square brackets. It 's OK.
        
               | dllthomas wrote:
               | > when you can tell through simple local reasoning that
               | the panic will never happen
               | 
               | A problem is that you may be able to tell now, but as
               | code changes and functions grow and split, that reasoning
               | may grow less simple and less local without any changes
               | of those lines in particular.
               | 
               | The odds of that, and the degree to which it matters, are
               | of course both spectacularly situation dependent.
        
               | dangerlibrary wrote:
               | For the first time, I find myself wanting the equivalent
               | of a :thumbs-up: reaction emoji on hn.
        
             | lumb63 wrote:
             | I'd be curious to see if the optimizer couldn't detect in
             | this instance that that code is, in fact, unreachable. It
             | seems to me like it should detect it with ease.
             | 
             | I will concede that the "you should never get here" type
             | errors are tempting to panic on. But I have seen so many
             | instances where the "you should never get here" code does
             | execute. Memory corruption, access by other processors,
             | etc., could make this check (which should never happen!)
             | actually fail at runtime. If one of those happens in code I
             | wrote, I would rather be alerted to the error, without
             | crashing, if possible.
             | 
             | A lot of the appeal to "panic on error" IMO is to reduce
             | developer cognitive load. As a developer, it's inconvenient
             | to consider all the errors that can actually happen when
             | programming. Allocations, array accesses, I/O, and other
             | errors programmers want to ignore, do happen. It's annoying
             | to think about them every time we write code, but I'd
             | rather have something that forces me to consider them,
             | rather than be bit by something that I designed out of my
             | consideration.
             | 
             | This preference might change depending on what I'm writing.
             | For a one-off script that I need to spin up quickly, I
             | don't really care. For code running on a device I may not
             | get to service for 5 years, my opinion would be different.
        
               | worik wrote:
               | > A lot of the appeal to "panic on error" IMO is to
               | reduce developer cognitive load.
               | 
               | Cognitive load is one of the main blocks to development.
               | Do not diminish the importance of lowering cognitive load
               | 
               | > This preference might change depending on what I'm
               | writing. For a one-off script that I need to spin up
               | quickly, I don't really care.
               | 
               | Yes, absolutely. Trade offs
        
               | justinpombrio wrote:
               | Wait hang on: your code hit a case that you _know_ means
               | something like memory corruption happened, and you want
               | it to _keep executing_? If there 's memory corruption, I
               | want the program to stop ASAP so that it does as little
               | damage as possible!
               | 
               | FYI Rust panics and illegal `[]` accesses will tell you
               | the file & line number where it happened.
        
               | zozbot234 wrote:
               | > A lot of the appeal to "panic on error" IMO is to
               | reduce developer cognitive load. As a developer, it's
               | inconvenient to consider all the errors that can actually
               | happen when programming. Allocations, array accesses,
               | I/O, and other errors programmers want to ignore, do
               | happen.
               | 
               | Rust has the ? operator to address the common pattern of
               | returning a failure condition to the caller. There's
               | effectively zero cognitive load involved, since compiler
               | hints take care of it when writing the code.
               | 
               | > I'd be curious to see if the optimizer couldn't detect
               | in this instance that that code is, in fact, unreachable.
               | 
               | Probably, but that should be a job for the type checker
               | not the optimizer. Ideally, there should be a way of
               | creating statically verified asserts about program
               | values, and plugging them in as preconditions to function
               | calls that require them for correctness.
        
             | tialaramex wrote:
             | There are way too many replies here which seem like they
             | fundamentally don't understand that you can't actually
             | write an algorithm to solve the problem they imagine is
             | easy.
             | 
             | Rust does _have_ Results that it knows can 't fail - for
             | genericity reasons - these are Infallible results, for
             | example try_into() when into() would have worked is
             | Infallible, Rust uses an empty type (equivalent to but for
             | now not literally identical to ! aka Never) for this, so
             | the compiler knows this type can't be reified, it needn't
             | do code generation for this never-happens error.
             | 
             | But there's a crucial distinction between _Rust 's
             | compiler_ can see this is Infallible and _I can show it won
             | 't fail_ and Rice's theorem means that gap is
             | _insurmountable_ even just in principle.
        
           | KRAKRISMOTT wrote:
           | If you are building application software like a web
           | framework, panicking and then dealing with the issue using a
           | global exception handler to return 500 is often the easiest.
           | There was no point in littering the code with match Result =>
           | err return http.status500 like bad Go.
        
             | dangerlibrary wrote:
             | This is sometimes called the diaper (anti)pattern - because
             | it catches all the poop. It works great - so long as the
             | only exceptions your program ever throws are ones you are
             | deliberately throwing to the global error handler. Given
             | that, go for it. ;)
        
             | kaba0 wrote:
             | (That would be the point of exceptions that make the
             | correct thing the majority of times, and why I think they
             | are superior, checked exceptions that it. They are
             | completely analogous to Result types, but are properly
             | supported by the language and can have as small or big
             | scope as needed. I am really hopeful that they will have a
             | resurrection with languages with effects)
        
           | jstimpfle wrote:
           | As an outsider, is panic() like assert()?
        
             | [deleted]
        
             | gpm wrote:
             | assert!(cond) in rust is basically                   if
             | !cond {             // I ad-libbed the exact error message
             | panic!("Asssertion failed {cond} was false, line/file
             | information, etc")         }
             | 
             | panic!(message) is "either throw an exception containing
             | message, or print the message + maybe a backtrace and abort
             | the process".
             | 
             | You can use compiler flags to guarantee that it aborts
             | instead of throwing an exception, you can't use compiler
             | flags to guarantee that it acts an exception, sometimes
             | rust will just abort the process (for example if you
             | panic!() while you're already unwinding because of a prior
             | panic!()).
             | 
             | If nothing catches the exception (and that's usually the
             | case, by convention), the runtime will print the message,
             | maybe a backtrace, and kill the thread that paniced.
        
             | PuercoPop wrote:
             | It is like printf to stderr and exit(1)
        
             | bluejekyll wrote:
             | assertions often get turned off in production/release code,
             | panics do not.
        
               | baby wrote:
               | Not in Rust. You can use debug_assert if you don't want
               | to assert in prod tho
        
               | bluejekyll wrote:
               | I meant generally, you are absolutely correct about rust.
        
           | burntsushi wrote:
           | Certainly not. See: https://blog.burntsushi.net/unwrap/#why-
           | not-return-an-error-...
           | 
           | But also the rest of the blog post should help clear up the
           | confusion.
        
           | compscigirl wrote:
           | Panics have a place in systems programming -- all operating
           | systems panic when fundamental guarantees are violated.
        
             | CyberDildonics wrote:
             | I think you mean that CPUs have low level
             | exceptions/interrupts. 'Panic' in this case is a rust
             | specific term and is higher level.
        
           | efficax wrote:
           | This is the right answer. A panic is a bug. It's always a
           | bug.
        
       | softirq wrote:
       | After 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 wrote:
         | [dead]
        
         | gnuvince wrote:
         | Bravo!
        
         | rascul wrote:
         | > I don't think I will ever like kitchen sink languages.
         | 
         | What makes a kitchen sink language?
        
           | matthewaveryusa wrote:
           | I 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.
        
             | rascul wrote:
             | Thank you for the explanation.
        
             | madmoose wrote:
             | > For C and Go, there really is only one prevailing idiom
             | on how to write code
             | 
             | What's the one idiomatic idiom in C for a growable list of
             | items? Because I must have a seen a hundred of those in my
             | time.
             | 
             | What's the one idiomatic idiom in C for a hashmap? I've
             | seen about a hundred of those too.
        
               | throwaway17_17 wrote:
               | I would think you are referring to implementations of the
               | one prevailing idiom as opposed to seeing multiple idioms
               | for the concept. The idiomatic way to represent a
               | growable list in C is to make a list (using some
               | implementation) and put it in a structure with
               | bookkeeping data, then using that list by passing it (or
               | a pointer to it) to plain old functions to use it.
        
               | madmoose wrote:
               | Of course I'm talking about an idiomatic implementation,
               | what else would I be talking about in this context?
               | 
               | > The idiomatic way to represent a growable list in C is
               | to make a list (using some implementation) and put it in
               | a structure with bookkeeping data, then using that list
               | by passing it (or a pointer to it) to plain old functions
               | to use it.
               | 
               | That sounds like a long-winded way to say "there's no one
               | idiomatic growable list in C".
        
             | bluejekyll wrote:
             | "For C..., there really is only one prevailing idiom on how
             | to write code"
             | 
             | You can look at single projects that have changed the way
             | they write C code over time, introducing new idioms all the
             | time. OpenSSL is a perfect example of that in regards to
             | their work to reduce memory related bugs. C's idioms have
             | changed so many times over it's life it's hard to keep
             | track or even read it from one era to the next.
        
             | ojosilva wrote:
             | Having more than one way of doing things is good when it's
             | a consequence of a positive, well-curated language
             | evolution. I think that's Rust case. It doesn't mean that
             | it will become more readable ("beauty"?) in the future,
             | there's a lot of line noise already: <'a>, foo::bar,
             | #[define(...)], etc.
             | 
             | Or, ehem, JS. I think the JavaScript -> ES6,7,8,9 evolution
             | is a good example of positive change: the language improved
             | immensely at the cost of having even more idioms, TIMTOWTDI
             | and transpilers like TS. The old parts are now marked as
             | bad practice and optionally linted to hell. You still have
             | to put up with W3schools-level code once in a awhile
             | though.
             | 
             | Perl and C++ to me would be some bad examples. TIMTOWTDI
             | hampered their evolution way too much. Perl froze for 10
             | years+. Python was much braver: their folks evolved (from 2
             | to 3) by breaking compatibility in order to remain true to
             | its Zen.
             | 
             | Zig and Go are examples of rigidity, one-way-only extreme,
             | which is a pain sometimes but, in Zig's case, maybe a good
             | practice for a young language project. Readability in Zig
             | is excellent and I like the choices with the @ functions
             | ie. @as or @ptrToInt() instead of piling-up parens and
             | asterisks. Definitely a case-study of readability and
             | evolution for the future to come.
        
             | throwaway17_17 wrote:
             | I don't think your definition is badly chosen. In fact I
             | think it is a pretty good demarcation between language
             | ideals. However, I would define 'kitchen sink language' as
             | one that has a bit of everything in the language itself and
             | has the syntax to 'support' those bits of everything. Like
             | a language that has lambdas, functions, methods, type
             | classes, value types, box types, Types of types, macros,
             | lifetimes, templates, multiple looping constructs... the
             | list can go on. I think the real exemplars for 'kitchen
             | sink' in this conversation is to differentiate between Go
             | and C++. The amount of concepts in C++ is staggering and Go
             | is in contrast brain dead simple. I think Rust is
             | absolutely on the C++ side of this divide and is primarily
             | what GGP was getting at in their comment.
        
             | Capricorn2481 wrote:
             | I 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.
        
         | wg0 wrote:
         | Point of higher level languages is and was productivity. Now
         | some languages have Turing complete type systems and I have
         | seen some clever usage of types where really even the author
         | might not know how it works given enough time passes without
         | touching the code base.
        
         | avgcorrection wrote:
         | > After 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).
         | 
         | The safety guarantees are the whole point compared to C. So
         | what's _the point_ of complaining about how things are
         | cumbersome when you just elide that whole side of the debate?
         | 
         | I could complain about how C is too cumbersome with the proviso
         | that I don't care about portability or efficiency. But then
         | what would the point be?
        
         | brigadier132 wrote:
         | > I have to say that I find Rust just as ugly and cumbersome as
         | C++
         | 
         | Ok, so you don't like it aesthetically. Do you have problems
         | writing unmaintainable software in it? What about incorrect
         | software? Is there any specific feature in the language that
         | you object to that will cause confusion and lead to people
         | writing bugs?
         | 
         | C is a very beautiful and "simple" language, people also write
         | a lot of security vulnerabilities with it.
         | 
         | Beauty is a useless concept in a programing language. Most of
         | the time it just relates to someone's bias towards what they
         | are familiar with. A lot of people find Python "beautiful", I'm
         | unfortunately very familiar with python and as I've become more
         | and more familiar with it I find it uglier and uglier.
         | 
         | Same with C, I remember all the many hours I've spent in
         | valgrind debugging problems other people have made for me and I
         | find it ugly too.
         | 
         | When I look at a programming language, I don't think about
         | aesthetic "beauty" or even "simplicity".
         | 
         | I think, does this programming language allow me to represent
         | the concepts I want to represent accurately and correctly?
         | 
         | If it is not memory safe, does not support static typing with
         | algebraic data types, and does not have null safety it does not
         | meet those minimum requirements and is not suitable for use.
         | 
         | Edit: I want to add, it's not just accuracy and correctness
         | that are important. Performance is very important too and many
         | functional languages absolutely flounder because of strict
         | immutability and the adoption of patterns that have terrible
         | memory access patterns.
        
           | softirq wrote:
           | > Beauty is a useless concept in a programing language.
           | 
           | Maybe not aesthetic beauty, but readability in many contexts
           | matters more than performance. Code that isn't readable hides
           | bugs and can't be maintained (FYI Rust doesn't stop you from
           | writing bugs into your code). A language like C or Go where
           | you can fit most of the syntax and mechanisms into your head
           | are simply easier to read and reason about than a language
           | where you need a PhD in type theory to understand one
           | signature.
           | 
           | > If it is not memory safe, does not support static typing
           | with algebraic data types, and does not have null safety it
           | does not meet those minimum requirements and is not suitable
           | for use.
           | 
           | You'd better stop interacting with technology then, because
           | the vast majority of it is still running something compiled
           | from C. We're talking about control systems, life saving
           | technology, technology that's running in outer space.
        
             | plandis wrote:
             | C is not what I would call a very readable language. I
             | don't think you could drop someone who newly learned C and
             | have them be effective looking at glibc for example.
        
               | queuebert wrote:
               | Because of the use or abuse of macros, different C
               | codebases can look very different, and to learn it you
               | have to figure out all the quirky macros. If people stick
               | to standard C with few macros and verbose variable names,
               | it's really not bad.
        
               | duped wrote:
               | void* (*identifier) (void*);              typedef struct
               | {             /* ... */         } my_struct_t;
               | static return_type         name_of_function (type
               | *restrict argument)           {                 int a, b,
               | c;                 /* body */           }
               | 
               | Just some examples of illegible syntax that's "standard"
               | C. C is not a pretty language. It's small, but it's ugly
               | as sin.
        
               | dcow wrote:
               | It's nearly impossible to actually parse anything more
               | complex than a pointer to a struct in C because it wasn't
               | written for humans, it is a relic of using a convenient
               | recursive descent parser to parse C's syntax that we end
               | up with `void* (*id) (void*)`...
        
             | Certhas wrote:
             | Are you familiar with the lengths people go to to make C
             | work in the "life saving technology" contexts?
             | 
             | Your argument is essentially: My thinking is clear and
             | correct, I just can't explain to the compiler why it's
             | correct, so just trust me.
             | 
             | People _very_ easily delude themselves into thinking their
             | thinking is clear. When they are forced to spell it out in
             | excruciating detail it often turns out otherwise.
        
             | satvikpendem wrote:
             | > _FYI Rust doesn 't stop you from writing bugs into your
             | code_
             | 
             | You are committing the perfect solution fallacy [0]. Rust
             | won't make you have no bugs in your code but don't conflate
             | some number of bugs with a reduced number of bugs, a
             | reduction is still a meaningful outcome, otherwise we'd
             | still be writing in assembly.
             | 
             | > _We 're talking about control systems, life saving
             | technology, technology that's running in outer space._
             | 
             | Indeed, that's why we _should_ use more memory safe
             | languages, because we are dealing with critical systems.
             | Just because they were written in C does not mean that they
             | should continue to be. It 's like digging a hole with a
             | stick, and when someone suggests a shovel or backhoe, you
             | mention that all previous holes were made with a stick.
             | There is no relationship between the previous work and what
             | should be done in the future.
             | 
             | [0] https://en.wikipedia.org/wiki/Nirvana_fallacy#Perfect_s
             | oluti...
        
               | softirq wrote:
               | > You are committing the perfect solution fallacy.
               | 
               | Rust is also not a perfect solution. You have to prove
               | that the benefits of solving for the bugs Rust can
               | prevent outweighs the downsides of asking a bunch of C
               | experts who are currently developing kernel subsystems in
               | C to stop what they are doing and rewrite millions of
               | lines in C in a language that has very low marketshare in
               | the space and is far, far more complex in terms of
               | abstracting over assembly. People write systems software
               | in C not because they have a fetish for bugs, but because
               | we do in fact still have to stay close to the hardware
               | and not get lost in a minefield of abstractions.
        
               | satvikpendem wrote:
               | > _Rust is also not a perfect solution._
               | 
               | Yes? That's explicitly what I said.
               | 
               | C is successful despite all the bugs, not because of it.
               | If there are newer methodologies that solve bugs, one
               | should use them, rather than sticking to their "tried and
               | true way" while suffering through segmentation faults.
               | Already parts of the Linux kernel as well as Windows are
               | being written in Rust, so if it's good enough for them,
               | it should be good enough for those bunch of C experts.
               | They also don't have to rewrite millions of lines, they
               | can incrementally improve the codebase.
               | 
               | All this to say that even in your above comment you're
               | still committing the perfect solution fallacy. You are
               | still talking about how Rust is not perfect even though
               | no one said it was, and you mention trying to convince
               | all of those C experts to rewrite millions of lines of
               | code when, again, no one said they have to do.
        
               | softirq wrote:
               | > Already parts of the Linux kernel as well as Windows
               | are being written in Rust, so if it's good enough for
               | them, it should be good enough for those bunch of C
               | experts.
               | 
               | The C experts I was referring to are the subsystem
               | maintainers and none of them are currently working on
               | migrating to Rust AFAIK. So far the work in Linux with
               | Rust has been a few driver rewrites. Keep in mind that
               | there are subsystems like eBPF, io_uring, etc. that are
               | under rapid development and are completely in C. I think
               | if you really believe that kernel code should be written
               | in Rust, you should start submitting patches instead of
               | telling people that they have some sort of moral
               | obligation to use Rust when you aren't the one that has
               | to do any of the work.
        
               | dcow wrote:
               | You aren't being charitable. Please stop fussing it's
               | rather immature. People have addressed your points,
               | please actually respond to theirs.
               | 
               | It's not helpful to keep saying "look there still some
               | people writing C" and use that as an argument for how C
               | has somehow solved the problem where C programers
               | regardless of how good they are ultimately write bugs
               | that lead to takeover of the program counter. Rust makes
               | the world better. If you don't want to pay the semantic
               | price of that then fine, but it means you're okay with
               | still writing horrible bugs. Some of us aren't. I promise
               | Rust's semantics are better than C++ even though Rust has
               | its rough edges and could be improved.
               | 
               | What would be helpful to your cause is to mention the
               | examples of people starting to add tooling to C to solve
               | problems people are solving with Rust, but without the
               | semantic price of Rust.
        
             | brigadier132 wrote:
             | > Rust doesn't stop you from writing bugs into your code
             | 
             | Obviously. It's not about Rust specifically. It's about
             | accurately representing state which is extremely difficult
             | to do in programming languages that do not have algebraic
             | data types.
             | 
             | In Golang how do I represent that a type is either type A
             | or type B and then in a type safe way perform logic if it's
             | type A or perform logic if it's type B? Algebraic data
             | types also allow for things like the result and option
             | type.
             | 
             | Throw in the borrow checker which prevents a host of
             | aliasing and concurrency bugs (not all). You are more
             | likely to write correct Rust than other languages.
             | 
             | > You'd better stop interacting with technology then,
             | because the vast majority of it is still running something
             | compiled from C.
             | 
             | Thank you for another lesson I already know. I clearly
             | meant I don't think it's suitable for use in new programs.
             | People will still use it regardless as they are free to do
             | so.
        
               | preseinger wrote:
               | > In Golang how do I represent that a type is either type
               | A or type B
               | 
               | generics provide one approach to this, but, in general,
               | you don't -- you find another solution to your problem
        
               | softirq wrote:
               | > extremely difficult to do in programming languages that
               | do not have algebraic data types.
               | 
               | And this is why people like me avoid ML based languages,
               | type astronauts, and shiny new toys. You're
               | misrepresenting inconveniences as fatal flaws when we've
               | been successfully running all of modern society on
               | kernels written in C for fifty years. Most of the kernel
               | subsystem maintainers aren't even considering Rust as
               | anything more than an experiment at this point. No one
               | cares about ADTs and the perfect representation of state
               | transitions in the type system when we have actual
               | problems to solve, and we can and have been solving them
               | in C.
        
               | satvikpendem wrote:
               | > _successfully running all of modern society on kernels
               | written in C for fifty years._
               | 
               | No we haven't. C is successful despite its major flaws
               | (seg faults based on memory errors are not mere
               | "inconveniences"). Microsoft mentioned for example that
               | around 25-33% of all bugs in Windows were based on memory
               | errors. They are currently cutting those down to 0% via
               | Rust.
        
               | biorach wrote:
               | > And this is why people like me avoid ML based
               | languages, type astronauts, and shiny new toys.
               | 
               | There's nothing new or shiny about the ML family. And the
               | growing interest in ADT's is about their very real
               | advantages in representing state and solving real
               | problems in a less error prone manner than C
        
               | vpastore wrote:
               | Don't argue with him. He is just and old man doing what
               | all old man do: find excuses to not learn new stuff and
               | convince himself that he didn't have to do it
        
               | dcow wrote:
               | Yeah I find it really hard to see how they're being
               | charitable. Just an old fart arguing for the continued
               | relevance of curmudgeon-y technology instead of actually
               | responding to the points being argued.
               | 
               | What people who still care about writing C are doing is
               | starting to use tools to bring some of what Rust offers
               | to C, because they acknowledge that avoiding entire
               | categories of bugs is actually really good, despite which
               | language you use. But... no mention of that here, it
               | seems.
        
               | brigadier132 wrote:
               | > And this is why people like me avoid ML based
               | languages, type astronauts, and shiny new toys
               | 
               | Instead you get things like this in GO
               | 
               | `var _ litestream.ReplicaClient = (*ReplicaClient)(nil)`
               | 
               | And every single variable is implicitly Type or null
               | because obviously the alternative is more complex
        
             | shpongled wrote:
             | Readability is a function of familiarity though. I have
             | been programming in Rust for > 5 years, I find it to be an
             | incredibly readable language, because I am so familiar with
             | it.
        
             | golergka wrote:
             | > readability in many contexts matters more than
             | performance
             | 
             | I completely, whole heartedly agree. Not just in many, but
             | in most contexts.
             | 
             | And Rust or C shouldn't be used in these contexts. Use
             | typescript or python instead.
        
           | jackmott42 wrote:
           | It isn't a question of aesthetics, the primary pragmatic
           | issue with languages that have many features is how hard it
           | can be to read a new code base. Tools like macros and traits
           | can save you a lot of typing but when someone else comes into
           | your code, it is much harder to come to terms with it than
           | simpler languages like C or Go where the set of features can
           | all be held in your head and the actual code is explicitly
           | stated rather than generated by macros and generics and such.
           | 
           | This is a natural tradeoff in language design, do you deal
           | with verbosity and boilerplate because the language has few
           | features, or do you deal with the cognitive overhead of
           | understanding all the features?
        
             | plandis wrote:
             | C has plenty of macro use doesn't it? Even something as
             | simple as a doubly linked list is implemented with lots of
             | macros in the Linux kernel AFAIK.
        
             | balder1991 wrote:
             | I think there's this concept in language theory that those
             | speaking or writing strive for more complex concepts as it
             | matches better their thoughts, while those receiving strive
             | for more simplicity as it takes more energy to make sense
             | of complex concepts (think about it as adapting the
             | language to your audience).
             | 
             | But I think the main problem here is the famous debate of
             | statically-typed languages vs dynamic ones. As statically-
             | typed languages add more and more features to be more
             | expressive, they have to work around the compiler
             | limitations and create very bizarre syntaxes if they want
             | to keep performance.
             | 
             | But maybe this is a tooling problem? What if we could see
             | the code in a simpler way even if the actual code involves
             | macros and templates?
             | 
             | Swift has just implemented macros that Xcode expand to show
             | the generated code in a collapse/expand way, making it easy
             | to understand what it does.
        
               | Firadeoclus wrote:
               | This. Being able to expand macros lets you learn what it
               | does in context, and once you're more familiar with the
               | abstraction it gives you the benefit of hiding the
               | details and highlighting the moving parts within an
               | otherwise static structure.
        
             | ElectricalUnion wrote:
             | > C or Go where the set of features can all be held in your
             | head and the actual code is explicitly stated rather than
             | generated by macros and generics and such.
             | 
             | The C preprocessor is a Turing-complete, and a lot of C
             | code abuses it to implements things a lot more complex that
             | just "macros and generics and such".
             | 
             | Because of that C with a preprocessor literally has a
             | infinite "set of features", a pretty big set of features
             | almost garanteed to not fit anyone's head. I don't think
             | you can call something with - both by it's own code and
             | it's libraries code - arbitrary turing-complete
             | preprocessing "understandable".
        
             | brigadier132 wrote:
             | I find Rust extremely easy to read. In comparison, I was
             | trying to read a Go codebase and found it hard to read. Of
             | course, I've spent multiple years writing Rust and almost
             | no time writing Go and I don't know the Go programming
             | language at all. It was very easy to "learn" of course. But
             | I didn't actually learn it given when I started reading
             | code bases I was interested in I started to see weird
             | things like
             | 
             | `var _ litestream.ReplicaClient = (*ReplicaClient)(nil)`
             | 
             | which I couldn't find an explanation for using normal means
             | so I asked chatgpt and it told me it's an
             | 
             | "interface compliance check" "This line is a way of
             | ensuring that a certain type (*ReplicaClient) implements a
             | certain interface (litestream.ReplicaClient)."
             | 
             | For some reason this pattern was left out of the official
             | documentation. Am I missing other patterns? I find Traits
             | much easier to understand.
             | 
             | Also, like many others I give coding interviews and people
             | writing Go always seem to run into null pointer errors. I'm
             | not making that up. (I have no sample for Rust programmers)
        
             | littlestymaar wrote:
             | But when all the features of the language fit in your head,
             | it usually means the code you're reading keep repeating
             | itself lacking more convenient features (yes Go, I'm
             | talking about your error handling).
             | 
             | The _needs_ of our programs are complex, so in the end this
             | complexity has to be somewhere, and it is either in the
             | language itself, or in our code...
        
         | PartiallyTyped wrote:
         | > where you spend a bunch of your time arguing with the type
         | systems
         | 
         | That's the point. It's a feature, not a bug.
         | 
         | The type system exists to reduce the space of unsound programs.
         | The trade off is dev time for runtime errors.
        
         | Weebs wrote:
         | There's definitely tradeoffs to some things, and I think in
         | particular Rust's static guarantees can cause additional
         | friction, but in general I've found ML derived languages to be
         | extremely practical because it's a good local maximum on the
         | graph in terms of complexity vs runtime safety (where languages
         | like Java add too much friction without enough benefit compared
         | to something like Python)
         | 
         | I've found in software development the 80/20 rule is extremely
         | true, but when you need something from that last 20%, it can
         | _really_ mess up your architecture trying to work around it. To
         | me this is why others love LISP so much, and I appreciate F#
         | /OCaml for getting me as close to that potential as possible
         | while keeping static analysis reasonable. Clever use of type
         | systems is great for RAD and making sure critical components
         | make certain classes of bugs a compile time error, and it can
         | turn into undecipherable magic, but the additional information
         | and guarantees advanced type systems provide allow me to focus
         | on my immediate problem instead of the interplay with a larger
         | system.
        
       | hresvelgr wrote:
       | There 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 wrote:
         | I 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.
        
         | nobleach wrote:
         | This is what it was like to read any Kotlin book for the past
         | few years. The general assumption was, "we all know Java, and
         | we're all using IntelliJ... so this is analogous to....". It's
         | useful for Java developers of course. It's not as useful to
         | those that were new to Android development.
         | 
         | I believe the reasoning here is that it's often easier to teach
         | by analogy. Rust is fairly difficult to approach as, "My First
         | Programming Language". On the other hand, C had to do exactly
         | this back in the day. Every book had to explain the stack and
         | the heap so that pointers would make sense.
        
           | masklinn wrote:
           | > I believe the reasoning here is that it's often easier to
           | teach by analogy.
           | 
           | The problem is less the analogy and more the analogy _to
           | C++_. Which is fine if you're targeting C++ devs, and C++
           | oriented rust guides are certainly researched (I saw queries
           | several times on  /r/rust). It's more problematic if you're
           | billing the guide as more general.
        
         | laeri wrote:
         | It is quite helpful in my opinion and I don't come from a C++
         | background (although I have some experience with it).
        
         | umanwizard wrote:
         | IMO (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 wrote:
           | Regardless 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.
        
           | the__alchemist wrote:
           | I agree with your general point, but its use case extends
           | beyond that specific case in a few areas. Eg, embedded, where
           | the analogy fails. Rust is a great fit for embedded. More
           | generally, it's a fit for any case where you need fast code,
           | low-level code, bare-metal code, or standalone executable.
        
           | avgcorrection wrote:
           | Git might be a good kind of advancement on Subversion but not
           | everyone who learns Git will have used Subversion.
        
             | robertlagrant wrote:
             | That might be more like comparing Rust to C, though.
             | 
             | This is more like the difference between Mercurial and Git,
             | which is to say, relatively minor.
        
           | Night_Thastus wrote:
           | Saying it's a "better" C++ is not very accurate. It's a very
           | different beast IMO, and not suitable for all applications.
           | There are areas where it shines, and areas where C++ would be
           | a better choice.
           | 
           | If you want to call it 'C++-like' that's fine, there are
           | similarities.
        
             | umanwizard wrote:
             | It's hard for me to imagine starting a new project in C++
             | today instead of Rust, unless I needed interoperability
             | with the pre-existing C++ ecosystem. What are some examples
             | of areas where C++ is better?
        
               | Night_Thastus wrote:
               | There are other reasons, but having access to the
               | mountain of existing C++ libraries (and C libraries) is
               | no small thing.
               | 
               | The shear number of hours that can be saved by using
               | existing known, well-understood, well-developed libraries
               | and tools is enormous.
               | 
               | It will be another decade or two before Rust hits that
               | point, if it does at all. It just takes time.
               | 
               | Also: Rust is a bit lower-level/aimed more at systems
               | programming and forces thinking more about memory and
               | structure than C++ does. That has advantages but isn't
               | always great for higher-level applications. I can whip
               | out a basic gui app using QT but that would takes ages in
               | Rust.
        
               | nordsieck wrote:
               | > There are other reasons, but having access to the
               | mountain of existing C++ libraries (and C libraries) is
               | no small thing.
               | 
               | That's true. But a lot of code has a shelf life.
               | 
               | People used to point to CPAN as a huge benefit for using
               | Perl. Most don't any more; while all that code is still
               | out there, a lot of it doesn't really do much that's
               | useful today. It was built for another time.
               | 
               | Some of the existing C/C++ code will rot in the same way.
        
               | Night_Thastus wrote:
               | I think code that is continually edited and expanded over
               | many years, with lots of changes to requirements, _if
               | managed poorly_ , can have a shelf life.
               | 
               | But there's plenty of older code that will continue to
               | work just fine essentially forever.
               | 
               | I use plenty of older code that hasn't been touched and
               | is fine. I also use libraries that have been continually
               | improved that have been fine. QT isn't perfect but it's
               | been around since '95 and I think that the modern state
               | of it is pretty great overall.
        
           | shrimp_emoji wrote:
           | To me, Rust feels like a better C.
        
       | vaylian wrote:
       | > 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.
        
         | sapiogram wrote:
         | What common misunderstanding?
        
           | vaylian wrote:
           | https://en.wikipedia.org/wiki/Learning_curve#%22Steep_learni.
           | ..
        
       | gnuvince wrote:
       | I 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 wrote:
         | For example?
        
           | gnuvince wrote:
           | > 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?
        
             | maleldil wrote:
             | > > 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.
             | 
             | The disclaimer at the top of the item agrees:
             | 
             | > The title of this Item would be more accurately described
             | as: prefer returning a Result to using panic! (but don't
             | panic is much catchier).
             | 
             | Later on:
             | 
             | > If an error situation should only occur because (say)
             | internal data is corrupted, rather than as a result of
             | invalid inputs, then triggering a panic! is legitimate.
             | 
             | Most of your criticism boils down to "general idea is fine,
             | but there are important special cases". Which is expected,
             | to be honest. It's difficult to give general guidance
             | that's correct for everyone.
        
             | rascul wrote:
             | > > 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.
             | 
             | There's an anti-pattern for that.
             | 
             | https://rust-
             | unofficial.github.io/patterns/anti_patterns/der...
        
       | sheepscreek wrote:
       | After 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).
        
         | afavour wrote:
         | > 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.
        
           | cesarb wrote:
           | That's probably a side effect of the borrow checker and its
           | lifetimes being the interesting new thing which is mostly
           | unique to Rust. The counted references Rc<> and Arc<> are
           | boring, they exist in plenty of other languages (for
           | instance, std::shared_ptr in C++). It's the same thing with
           | Haskell and monads: every Haskell guide is going to focus on
           | monads.
        
             | afavour wrote:
             | Oh, I agree. But the result is that I often see people say
             | "don't use Rust for general purpose programming, it's too
             | complicated" and in my experience it's actually a great
             | general purpose programming language. You just have to
             | reach for the tools given to you rather than push through
             | the hard way.
        
           | estebank wrote:
           | The first thing I tell people is ".clone() it, Box it, Arc
           | it. You can make it faster later."
           | 
           | Experienced devs see an Arc<RwLock<T>> and are tickled by the
           | "obvious" performance impact, but then they try to use higher
           | ranked lifetimes before fully internalizing the rules and get
           | frustrated. The Rust Way there is to look at how you can
           | reframe the problem to minimize the pain, because that pain
           | is a symptom of an ownership web that's too complex.
           | 
           | If you use the wrapper types while you learn the language, it
           | makes it easier to focus _only_ on learning the lifetime
           | rules, instead of trying to learn the whole language at once.
        
           | mrbonner wrote:
           | +1 this is that my understanding as well regarding Rc. I
           | can't recall where I read but it is advised against using Rc
           | for "not being the Rust way". Do you have any references in
           | doc or code that have Rc usage I can read?
        
           | mwcampbell wrote:
           | How 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.
        
             | afavour wrote:
             | I don't think we disagree here. I agree that not using Rc<>
             | is the optimal position but I find myself wondering how
             | many Rust beginners got tangled up in lifetimes, gave up
             | and went back to a garbage collected language. I think it's
             | fine to make an on-ramp that's a little shallower.
        
               | insanitybit wrote:
               | I'd suggest using `.clone()` over `Rc` since it'll be
               | easier. Rc is an optimization to make `.clone()` cheap,
               | but the downside is it makes mutability more complex.
        
             | harpiaharpyja wrote:
             | My experience is that applications get slow when they are
             | loaded down with bajillions of layers of unnecessary bloat.
             | 
             | It's not because of micro-decisions such as whether to use
             | reference counting.
        
               | the-smug-one wrote:
               | A bajillion layers of unnecessary indirection is gonna
               | have the same consequences.
        
               | insanitybit wrote:
               | Your statements seem contradictory. "bajillions of layers
               | of bloat" sounds like exactly the situation of "many
               | micro-decisions" that degrade performance.
        
             | sheepscreek wrote:
             | Here's another way to frame the original argument:
             | 
             | A less performant solution in a subset of Rust will still
             | be miles ahead of optimized Python or Ruby, while being as
             | readable. An equivalent C++ solution on the other hand
             | could be less readable and unsafe.
             | 
             | Also, limited use of borrowers is not that hard to grasp.
             | Mutable references however make my head hurt. More so when
             | they're used by someone who doesn't understand their
             | purpose well.
             | 
             | One mutable reference passed down a chain of functions is
             | okay (like a shared context/state object/db
             | connection/etc), but more than a few make my head hurt.
             | 
             | You can still make logical mistakes with shared context, so
             | in general - more pure functions = easier to spot logical
             | mistakes (as most mutations will be concentrated in one
             | place). And this is a general rule applicable to all
             | programming languages.
        
         | fallat wrote:
         | > After _glancing_ through this, I can confidently say
         | 
         | Yeah ok.
        
       | mrbonner wrote:
       | I 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 wrote:
         | > (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 wrote:
           | My 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.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.
        
             | MuffinFlavored wrote:
             | > use only when needed
             | 
             | I don't know when to use them/I probably just go for
             | Arc<Mutex<T>> instead when I probably shouldn't?
        
               | cmrdporcupine wrote:
               | You only need the Arc<Mutex if you have multiple threads
               | touching something. And don't worry, the compiler will
               | let you know you gone-messed-up before you get there by
               | angrily telling you about your lack of Send and Sync all
               | over.
               | 
               | Honestly, if you are in a multithreaded world, you are
               | going to make your life a _lot_ easier by reducing your
               | shared mutable state and instead using channels
               | (crossbeam or mpsc) to communicate and coordinate. This
               | will reduce your need to toss things in Arc <Mutex, and
               | in fact reduce a lot of borrow checker issues generally.
        
               | MuffinFlavored wrote:
               | Say I'm "single threaded" (more simple). When to use
               | Rc/RefCell then?
        
               | duped wrote:
               | You use `Rc` when a value can have more than one owner.
               | You use `RefCell` when you need multiple mutable
               | references to a value in scope (aka interior mutability -
               | there are more than one mutable references held, but the
               | borrow checker can't prove that they don't overlap at
               | compile time).
        
               | cmrdporcupine wrote:
               | As I said above, roughly:
               | 
               | Rc for if you have some piece of data shared between
               | (non-concurrent) components, that is maybe hard to manage
               | with borrowing? I personally would try to fix this rather
               | than rely on Rc, but there are legit cases.
               | 
               | RefCell for if you want to manage the borrowing at
               | _runtime_ instead of at compile. Basically it will raise
               | a panic at runtime if more than one person borrows off
               | it.
               | 
               | And it allows you to borrow something mutably
               | (borrow_mut()) even if the surrounding thing is immutable
               | or there are other mutable references. People use this
               | for the "interior mutability pattern"; Additional
               | examples would be e.g.: I have an internal counter I want
               | to increment every time you call this function, and
               | that's mutable, but the surrounding ref to the struct is
               | immutable and I want to keep it that way. I could keep
               | the counter in a RefCell and then borrow it immutably
               | into a RefMut even tho the context I'm doing it in is
               | immutable.
               | 
               | It's basically "unsafe" code, really, buyer beware. Apply
               | carefully.
        
         | duped wrote:
         | I write Rust professionally.
         | 
         | If you're using Rc you probably mean Arc. And most of the time
         | that you mean Arc you mean Box.
         | 
         | If you're using RefCell you probably have a bad data model. The
         | use case is interior mutability. This turns out to not be that
         | useful in practice if you designed your DS well. Occasionally
         | you need it in the design of some internals, but it's rare (and
         | rare to expose)
         | 
         | If you're fighting the borrow checker you're not writing
         | idiomatic Rust.
         | 
         | > I mean how much of an overhead if RC comparing to the real
         | borrow checker coming from Java or JS world?
         | 
         | Negligible, but the real question is "why do you need RC in the
         | first place?"
        
           | ramranch wrote:
           | There are certainly areas where idiomatic Rust needs to fight
           | against the borrow checker. If that wasn't the case, things
           | like `ouroborous` and `pin_project` wouldn't be as widely
           | used as they are.
        
             | duped wrote:
             | Day to day rust users shouldn't care too much about
             | pin_project since the big use case for pin is handrolled
             | futures and executors. Most folks just use adaptors from
             | the futures crate and one of a handful of executors in the
             | ecosystem.
             | 
             | I've never actually heard of that other crate. It looks
             | like most of its downloads come from its direct/indirect
             | dependents like glium. I'd say if you're writing self
             | referential data structures you're not writing idiomatic
             | Rust.
        
       | ojosilva wrote:
       | Does 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.
        
         | idoubtit wrote:
         | From 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
         | 
         | 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/sr...
        
         | awestroke wrote:
         | I 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
         | 
         | Just make sure to unselect chapters that aren't part of the
         | "book"
        
       | boredumb wrote:
       | Thanks! 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.
        
         | harpiaharpyja wrote:
         | Occasionally 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.
        
         | weavie wrote:
         | The 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.
        
           | laeri wrote:
           | Can you also use `unwrap_or_default` in this example?
        
             | Omin wrote:
             | No, 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 wrote:
               | Thanks 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.
        
           | tveita wrote:
           | From 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.
        
           | umanwizard wrote:
           | > 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.
        
           | [deleted]
        
           | jpochyla wrote:
           | This 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.
        
           | HankB99 wrote:
           | I 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 wrote:
             | The 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 wrote:
           | > 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 wrote:
         | That 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.
        
         | bryanlarsen wrote:
         | Pedantic 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 wrote:
           | Very cool, running it on the project I have open immediately
           | started barfing at me over using 'return' in quite a few
           | spots.
        
       | fortylove wrote:
       | This isn't exactly on topic, but is anyone else experiencing Rust
       | fatigue?
        
       | svieira wrote:
       | 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-
       | exp...
        
         | [deleted]
        
         | ben-schaaf wrote:
         | > 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 wrote:
           | > This is still true
           | 
           | Not since the addition of ControlFlow.
        
         | masklinn wrote:
         | The 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), 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).
         | 
         | 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.
        
         | ezrast wrote:
         | The "relevant From trait implementations" are the ones defined
         | on the value _wrapped_ by the Option or Result.
        
       | bilekas wrote:
       | The majority of this is not specific to Rust and not exactly
       | groundbreaking stuff, certainly a good reference material for
       | effective engineering.
        
         | jmcomets wrote:
         | +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-minim...
        
           | chrismorgan wrote:
           | I 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.)
        
       | lexicality wrote:
       | Immediately 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.
        
         | idlewords wrote:
         | As a heuristic, you should treat obvious errors in the body of
         | a technical text as a red flag, but don't be so harsh on
         | mismatched examples or diagrams. That stuff is notoriously easy
         | to mess up and doesn't tell you anything useful about the
         | amount of thought or revision that went into the writing of the
         | piece.
         | 
         | A more constructive thing to do than blaring the "FAIL" klaxon
         | at the first such mistake is to just let the author know.
        
           | lexicality wrote:
           | Writing is hard but a cursory proof reading would immediately
           | spot that the first line of the error is completely unrelated
           | to the description.
           | 
           | What other more subtle errors are also there?
        
             | beej71 wrote:
             | Speaking as someone who writes and puts stuff online, there
             | are probably approximately 1.2 zillion subtle errors.
             | 
             | The best thing you can do is point them out so the author
             | can fix it for posterity.
             | 
             | In a classic publishing environment, sure, edit the shit
             | out of it before launch. But this isn't that environment.
             | The fastest way to get quality, free content out there is
             | to publish something and then have everyone tell you what's
             | wrong, fix it, and move on.
             | 
             | Working together this way, we produce libraries of material
             | for anyone to use.
        
         | TazeTSchnitzel wrote:
         | This is really weird code because `print!` in Rust always takes
         | a format string (similar to C's printf but as a macro), but I
         | don't see one here. So it's not just a single-character typo.
        
           | [deleted]
        
           | dan-robertson wrote:
           | If you read the text, which has since been fixed, it appears
           | that the example output was incorrect and a print_page
           | function was intended.
        
         | xwowsersx wrote:
         | It 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 wrote:
           | If 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.
        
             | [deleted]
        
             | cdirkx wrote:
             | I 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.ht...
        
             | bogeholm wrote:
             | The book is written in mdbook which has built-in support
             | for testing Rust examples [0], as well as running examples
             | on the playground [1].
             | 
             | [0]: https://rust-lang.github.io/mdBook/cli/test.html
             | 
             | [1]: https://rust-
             | lang.github.io/mdBook/format/mdbook.html#rust-p...
        
       ___________________________________________________________________
       (page generated 2023-06-15 23:02 UTC)