[HN Gopher] Rust Language Cheat Sheet
       ___________________________________________________________________
        
       Rust Language Cheat Sheet
        
       Author : arunc
       Score  : 338 points
       Date   : 2021-04-25 07:10 UTC (15 hours ago)
        
 (HTM) web link (cheats.rs)
 (TXT) w3m dump (cheats.rs)
        
       | rob74 wrote:
       | Cool, but doesn't really make Rust less intimidating for novices
       | I'm afraid. I mean, it's been some time since I last saw a
       | language that needed a cheat-sheet entry for _concatenating
       | strings_ :                 format!("{}{}", x, y)
        
         | oblio wrote:
         | I'm not sure I understand why format is a macro and print and
         | equivalent in most other programming languages is a regular
         | function. There's some complexity there that I guess I just
         | don't know.
         | 
         | For the second part, Rust should probably add some sugar a la
         | Python f-strings (or just plain old string interpolation dating
         | back to the Bourne shell):
         | 
         | format!("{x}{y}")
        
           | CryZe wrote:
           | This is actually planned already. Not sure how far along the
           | implementation and progress to stabilization is though.
        
             | oblio wrote:
             | When you say planned, do you mean it's in some roadmap? I
             | couldn't find anything after a quick googling session.
             | 
             | Edit: Found the RFC: https://rust-
             | lang.github.io/rfcs/2795-format-args-implicit-i... - but I
             | don't see any sign that it has been accepted :-?
             | 
             | Edit II: I had missed the PR and issue at the top :-)
        
               | gpm wrote:
               | It's already in nightly too
               | 
               | https://play.rust-
               | lang.org/?version=nightly&mode=debug&editi...
        
               | oblio wrote:
               | Ah, nice! Anything that makes Rust more approachable will
               | help.
        
           | burntsushi wrote:
           | > There's some complexity there that I guess I just don't
           | know.
           | 
           | It's because the format string itself is checked to be
           | consistent with the types of arguments given. If there's an
           | inconsistency, you get a _compile_ time error. Most printf-
           | style functions in other programming languages don 't do
           | this.
        
             | oblio wrote:
             | Ah, that's awesome and it makes a lot of sense, plus it's
             | super helpful.
        
               | masklinn wrote:
               | There are other more "negative" reasons too:
               | 
               | The language doesn't have varargs (to say nothing of
               | type-safe generic variable-type args), so you'd have to
               | pass in a slice of references e.g.
               | print!(..., a, b, c)
               | 
               | would become                   print(..., &[&a, &b, &c])
               | 
               | Which is not exactly sexy, plus all these references
               | would have to implement a matching trait, whereas
               | currently formatting different values can involve
               | different traits depending on the format specifier e.g. a
               | plain `{}` is Display but `{:?}` is Debug and `{:x}` is
               | LowerHex. You can have a type which implements LowerHex
               | but neither Debug nor Display if that makes sense.
        
           | unrealhoang wrote:
           | format and co (write, print, ...) are macros because Rust
           | doesn't have variadic functions. Also, the format string and
           | its parameters are checked at compile time (number of params
           | match number of field, and type), other language I know don't
           | have this feature.
        
             | oblio wrote:
             | > Rust doesn't have variadic functions
             | 
             | By design or because nobody has implemented them, yet?
        
               | unrealhoang wrote:
               | it's a hard design problem, actually. But I think with
               | const generic, we are getting very close, variadic could
               | be:                   fn foo<const N: usize, T>(varied:
               | [&T; N]) { ... }         // usage         foo([&a, &b,
               | &c])         foo([&a, &b, &c, &d])
        
               | iudqnolq wrote:
               | If I'm reading your intent right that would monomorphize
               | to a slice of a specific N, meaning you couldn't express
               | format!("{}{}", 1, 1.0)
        
               | gpm wrote:
               | I think you're reading the comment you replied to right,
               | but I don't (completely) agree with your conclusion.
               | 
               | There are two magic things the format macro does apart
               | from varargs, it checks the format string against the
               | length and format types of the argument at compile time,
               | and it automatically borrows the arguments. Implementing
               | format with varargs means you need to lose those.
               | 
               | Once you lose those calling it looks like
               | format("{}{}", &1, &1.0)
               | 
               | under the above proposal the type signature of format
               | would look like                   fn format<const N:
               | usize>(fmt_str: &str, args: [&dyn Format; N]) ->
               | Result<String, Error>;
               | 
               | Where `Format` replaces the various `Display`, `Debug`,
               | etc traits and looks something like this
               | trait Format {             fn display(&self, fmt: &mut
               | Formatter) -> Result<(), Error> {                  //
               | Default implementation of this method
               | return Err(Error::NotImplemented)             }
               | fn debug(&self, fmt: &mut Formatter) -> Result<(), Error>
               | {                  // Default implementation of this
               | method                  return Err(Error::NotImplemented)
               | }                      ...         }
               | 
               | I.e. the trick is that the conversion to a trait object
               | happens (implicitly) on the calling side when you take a
               | reference, so all the arguments are all of the same type.
        
               | iudqnolq wrote:
               | That's a much better way of putting it. I was mostly
               | trying to point out the missing dyn, but I never
               | explicitly said that
        
               | gpm wrote:
               | Nobody has implemented them or designed them yet. No
               | obvious reason you couldn't come up with a satisfying
               | design, but there's consensus building work there around
               | "this is a good feature and the right design" as well as
               | implementation work.
        
           | geofft wrote:
           | > _I 'm not sure I understand why format is a macro and print
           | and equivalent in most other programming languages is a
           | regular function. There's some complexity there that I guess
           | I just don't know._
           | 
           | It's a really good question! It's a little bit complex but
           | hopefully not too much, and it shines a light on some
           | interesting differences between languages.
           | 
           | The answer is roughly that Rust is strongly typed, and
           | println!, format!, etc. all accept arbitrary types. They all
           | also accept a varying number of arguments, and Rust only
           | allows you to define functions with a fixed number of
           | arguments. Python, sh, etc. have neither of these
           | restrictions.
           | 
           | What would the type (function signature) of
           | println/format/etc. be if they were regular functions?
           | fn println(format: &str, arg1: SomeType?, arg2??);
           | 
           | You could solve the variable-number-of-arguments problem in
           | Rust itself somehow, maybe                   fn
           | println(format: &str, args...: SomeType);
           | 
           | but you'd have a few problems remaining.
           | 
           | First, you'd need to figure out what that type is. The
           | formatting macros don't just accept things that implement
           | Display - they also accept things that implement Debug if you
           | use {:?}, things that implement LowerHex if you use {:x},
           | etc. And the macro wants to return a compile-time error if
           | you mismatch types and the formatting string, like
           | println!("Hex {:x}, debug {:?}", [1, 2, 3], 4);
           | 
           | And second, and a little more simply, the macro wants to
           | return a compile-time error if you just have the wrong number
           | of things being formatted:                   println!("Three
           | things: {} {} {}", 1, 2);
           | 
           | In order to do that level of checking based on the value of
           | the string, it has to be some sort of compile-time extension.
           | In Python, you'd just get a runtime error and in bash, you'd
           | just get empty strings.                   >>>
           | "{:x}".format("hi")         Traceback (most recent call
           | last):           File "<stdin>", line 1, in <module>
           | ValueError: Unknown format code 'x' for object of type 'str'
           | >>> "{} {} {}".format(1, 2)         Traceback (most recent
           | call last):           File "<stdin>", line 1, in <module>
           | IndexError: tuple index out of range
           | 
           | Regular functions in Rust (and any other language) don't get
           | to see their arguments _at compile time_ ; they only see
           | their arguments when they're called.
           | 
           | C, of course, just ignores this problem entirely. You don't
           | get an error if you mess up the format string; you just get
           | invalid reads to memory.                   printf("Is this a
           | pointer? %s", 1234);         printf("What's the third thing?
           | %d %d %d", 1, 2);
           | 
           | Also (especially) in C, it's very easy to accidentally let
           | the format string be input external to your program:
           | printf(username);
           | 
           | which means that an attacker can specify the format string
           | and cause reads to memory you weren't expecting and cause
           | data to be leaked (or worse, an attacker can cause _writes_ :
           | C's %m token _stores_ the number of bytes written to the next
           | argument to printf, interpreted as a pointer - or the next
           | thing that happens to be memory, if you didn 't pass an
           | argument for it). Rust protects against this by making you
           | use a string literal as the first argument.
           | 
           | Python also protects against this by having f-strings be a
           | special part of the language syntax. You can't have an
           | f-string that's a variable, so an attacker can't set their
           | username to f"{DATABASE_PASSWORD}". So, in that sense, the f
           | sigil in Python is also a macro, like in Rust - it's just
           | shorter syntax.
        
             | ryl00 wrote:
             | > C, of course, just ignores this problem entirely.
             | 
             | Many C/C++ compilers special-case printf, and can actually
             | catch some printf format string problems at compile time.
             | For example, gcc 5.4.0 and msvc 19 warned about both of the
             | dodgy printf usages in your example (the type mismatch and
             | the wrong number of arguments).
        
         | gourlaysama wrote:
         | To be fair, this is more a Rust Syntax Reference Sheet than any
         | kind of cheat-sheet. It basically has everything.
         | 
         | It's useful to quickly lookup the syntax for something you
         | never use, for example. Or to browse and discover things you
         | didn't know were even possible.
        
         | yakubin wrote:
         | A simple "+" works as well, as long as "x" is a String, and not
         | a &str.
         | 
         | Also, concatenating strings for any other purpose than printing
         | them is a rarely done thing in my experience, no matter the
         | language. So I don't see why we should optimize the ergonomics
         | of something like that.
        
           | the_mitsuhiko wrote:
           | String concatenation is an antipattern in general. Making it
           | too convenient is not a great idea anyways.
        
             | moldavi wrote:
             | Why would concatenating strings be an antipattern?
        
               | talideon wrote:
               | There are lots of reasons it can be. Here's an example:
               | in many languages, concatenation requires reallocation to
               | generate a new string object. This is especially the case
               | where strings are immutable. That can get expensive
               | quickly as you're producing lots of garbage for little
               | benefit, and each concatenation requires progressively
               | larger and larger reallocations. You end up seeing
               | quadratic runtime costs in terms of both memory and
               | speed. There are a few different approaches taken to
               | this. In Python, the popular option is to append
               | everything to a list, and use `''.join(lst)` at the end
               | to concatenate everything. Java and C#, on the other
               | hand, have StringBuffer/StringBuilder types to accomplish
               | something similar.
               | 
               | Generally, you want to avoid simple string concatenation
               | in loops because of this, and it's not always obvious
               | when string concatenation is being done in a loop, such
               | as where the string concatenation is being done in a
               | method that's being called by some other code.
        
               | kevincox wrote:
               | String concatenation isn't expensive in Rust. Since the +
               | operator consumes the first argument it will reuse the
               | buffer and allocate quadratically. The only possible
               | performance downside is that it can't count all of the
               | lengths in advance and do a single allocation.
        
               | estebank wrote:
               | Add<&str> for String in a loop isn't as problematic as
               | adding two, for example, Java Strings because of the
               | capacity growth curve and buffer reuse, but it is still
               | worse than not having a `String::with_capacity` call
               | before the loop. I am sad to see that clippy doesn't have
               | any lints for this, but I can see how it would be hard to
               | make a general good suggestion for it.
        
               | [deleted]
        
               | burntsushi wrote:
               | Because it's very easy for it to become accidentally
               | quadratic in a loop.
        
         | s_brady wrote:
         | I really want to like Rust, I like the intention of what the
         | language is trying to do. But in a universe where languages
         | like Smalltalk, Eiffel and Lisp exist, it just feels like a
         | step backwards. Surely the computer should be doing the heavy
         | lifting? What happened to my bicycle for the mind? Just my
         | opinion. I admire the work going on in Rust but the complexity
         | makes me sad.
        
           | conradludgate wrote:
           | Well. I think they're very different languages for different
           | purposes. It's trying to replace C/++, and in those languages
           | you do have to pay a lot more attention otherwise you'll have
           | the occasional use-after-free or unchecked memory leaks etc.
           | They can be extremely problematic and for a human it's
           | impossible to catch everything.
           | 
           | Rust helps lift than mental load because if you're in safe
           | rust then you knows the compiler will catch these problems
           | for you. It also makes quite explicit when you're on the
           | stack or heap.
           | 
           | I also love the explicit mut keyword makes functions very
           | easy to reason about. In Go I find pointers being used for
           | three purposes, to act as a reference, to act as an optional
           | (nullable) or to act as a mutable parameter, and it's
           | impossible to tell which by looking at the function signature
           | and I can't tell you how many bugs that's caused in our
           | codebase
        
           | cytzol wrote:
           | > What happened to my bicycle for the mind?
           | 
           | The bicycle you pick depends on the problem you're trying to
           | solve. You can divide up all programming languages into two
           | categories:
           | 
           | 1. Those that allow you to state your problem in terms of
           | _ideas_. You write your code, the compiler or interpreter
           | does some optimisations, and the end result runs fast enough
           | for your use case.
           | 
           | 2. Those that allow you to state your problem in terms of
           | _machine instructions_. You demand a certain level of
           | performance, or want every memory allocation to be explicit.
           | The compiler or interpreter still does some optimisations,
           | but instead of inserting more instructions implicitly if it
           | has to, you 'd rather your program straight-up fail to build.
           | 
           | The choices made by a language's designers will make it seem
           | unnecessarily complicated if you're looking at a (2) language
           | when you have a (1) problem, and will seem like it's making
           | too many assumptions for you if you're looking at a (1)
           | language with a (2) problem.
           | 
           | For example, let's go back to string concatenation, which, as
           | you saw in Rust, looks like this:
           | format!("{}{}", x, y)
           | 
           | And, yes, in other languages it looks like one of these two
           | things:                   x + y         concat(x, y)
           | 
           | If all you want is to end up with a "String" that's made up
           | of those two other strings in it, this is good enough. I've
           | done this bajillions of times over the course of my career.
           | (Truly, I am a string-concatenating expert.)
           | 
           | But a String in Rust, under the hood, contains a pointer to a
           | heap-allocated buffer. And getting one of those requires a
           | memory allocation, and Rust makes those explicit. So the
           | method of concatenating two strings you choose depends on
           | what machine instructions you pick:
           | 
           | * If you don't really care, or if you _need_ a new heap-
           | allocated buffer, then `format!( "{}{}", x, y)` will give you
           | what you want.
           | 
           | * If `x` is already a heap-allocated String, and you know for
           | a fact that you aren't going to need to use it again, then
           | you can re-use the existing buffer with simply `x + y` (or
           | something like `x.push_str(y)`).
           | 
           | * If `y` is a heap-allocated String but `x` isn't, you can
           | re-use _that_ existing buffer with `y.insert_str(0, x)` (but
           | this requires it to copy the bytes in the buffer to make room
           | for the string you 're inserting).
           | 
           | * If neither is a heap-allocated String, and you don't want
           | to allocate any more memory, you'll have to skip
           | concatenation altogether and do something else (for example,
           | if you're just writing the concatenated string somewhere, and
           | don't need a buffer to put it in, then you can do
           | `write!(somewhere, "{}{}", x, y)` which won't allocate).
           | 
           | > Surely the computer should be doing the heavy lifting?
           | 
           | I find that the Rust compiler does an _excellent_ job of
           | doing the heavy lifting for me: I don 't have to worry about
           | iterator invalidation or use-after-free, surprise memory
           | allocations or numeric type conversions, or unexpected
           | performance drops when I make a small change. But this is all
           | because I'm expecting a type (2) language, rather than a type
           | (1) language.
           | 
           | It's the last part I'd like to focus on, because you may have
           | read the above list and thought "a compiler could choose the
           | appropriate optimisation for me!". And, well, it probably
           | could! After all, if the only difference between the first
           | option `format!("{}{}", x, y)` and the second option `x + y`
           | is whether the string `x` gets used after concatenation or
           | not, a compiler could certainly check whether this happens
           | and optimise out the allocation if necessary.
           | 
           | But then you need to worry about keeping this optimisation
           | when the code changes. Sticking with the example, suppose you
           | have code like this in some hypothetical (1) language that
           | allows you to express ideas but optimises them the best it
           | can, where `x` and `y` are Strings still:
           | var frobozz = x + y;
           | 
           | And then someone on your team makes this change:
           | var frobozz = x + y;         /* skip a couple dozen lines of
           | code */         var gunther = x + z;
           | 
           | A code change that starts using `x` for a second time now
           | results in an allocation being introduced a couple dozen
           | lines above from where the code change was, because it can
           | now no longer be optimised out. If this is still good enough
           | for you, because you're solving a (1) problem, then there's
           | nothing wrong with this. But if you're solving a (2) problem,
           | then the language has kind of... let you down.
           | 
           | So don't be sad! It just depends on what problem you want the
           | language to solve for you.
        
             | pjmlp wrote:
             | The only fallacy is when one then goes to Goldbolt and then
             | reaches the conclusion that regardless of the language, the
             | kind of generated Assembly can be the same, depending on
             | the proficiency of the author on the language features at
             | their disposal, and the compiler backend being used.
        
           | de6u99er wrote:
           | Smalltalk is still a thing?
        
             | pjmlp wrote:
             | Yes, https://pharo.org/
             | 
             | As for commercial versions,
             | 
             | https://www.cincomsmalltalk.com/main
             | 
             | https://www.exept.de/en/smalltalk-x.html
             | 
             | https://www.instantiations.com/vast-platform
             | 
             | And its IDE spirit lives on Java IDEs and Visual Studio
             | Code (as Erich Gamma is on the team).
        
           | emma-w wrote:
           | Isn't it true that Rust is meant to solve problems in spaces
           | where those languages really can't play? I'll admit to plenty
           | of ignorance about all three of those languages, so I'm
           | genuinely curious what you think.
        
           | hctaw wrote:
           | In my experience lisps are orders of magnitude more complex
           | than Rust, and require extensive debugging to be certain that
           | the code works. Rust is mostly a compile and run experience,
           | idiomatic Rust is hard to screw up.
        
           | pjmlp wrote:
           | This is why while I toy with Rust, I can only see it
           | replacing C and C++ (long term), in the context of kernels,
           | device drivers and real time scenarios where any kind of
           | memory allocation is a no-go (MISRA style).
           | 
           | For anything else, it isn't worth the productivity drop of
           | automated memory management (regardless in what form) coupled
           | with value types.
        
             | mxcrossb wrote:
             | I'm pretty sure that's the point. But since it's trendy
             | people try to apply it to everything.
        
               | cytzol wrote:
               | > But since it's trendy people try to apply it to
               | everything.
               | 
               | When people do things you don't understand, don't just
               | say that they're just doing it because it's fashionable.
               | Sure, they might be -- or they have their own reasons.
               | 
               | I use Rust in a couple of places outside of its original
               | "systems programming" niche, where, yeah, I don't really
               | need to track every allocation, I don't care about GC
               | pauses, I don't need to ship one single binary, and the
               | overhead of a runtime wouldn't bother me. Things like Web
               | servers for side projects, or small scripts to do a task
               | I need to automate.
               | 
               | However, I found that:
               | 
               | * The effort it took to bring Rust out of its niche, and
               | the time it took to learn the domain-specific libraries
               | for my use cases (Rocket for Web stuff; duct for shell
               | script stuff) was less than I thought;
               | 
               | * The amount of knowledge I needed to retain to use a
               | programming language effectively -- the components of its
               | standard library, common third-party helper libraries,
               | how to navigate the documentation, how to fix mistakes,
               | how to avoid traps and pitfalls, how to structure your
               | program, how to handle differences between language
               | versions -- was _much larger_ than I thought!
               | 
               | So I stick with Rust for non-systems tasks because the
               | benefits outweigh the detriments for me.
               | 
               | (Granted, I was only able to do this because I already
               | knew my way around the language and the borrow checker;
               | if you already know, say, Python, you can make this exact
               | same argument in reverse. But then you need to know how
               | to wield Python for low-level programming as well as
               | high-level programming.)
               | 
               | I read an article a few years ago called Java for
               | Everything[1] (which was discussed here on HN[2]) that
               | makes the same point, only with Java. If I had to pick an
               | "everything language", I don't think it would be 2014-era
               | Java, but the article _did_ sell me on the benefits of
               | having an  "everything language" in the first place, and
               | I feel the same benefits apply here with Rust.
               | 
               | [1]: https://www.teamten.com/lawrence/writings/java-for-
               | everythin... [2]:
               | https://news.ycombinator.com/item?id=8677556
        
               | hu3 wrote:
               | Some go as far as saying that "Rust is pretty accessible
               | to developers coming from scripting languages
               | (JavaScript, Python, etc)." [1].
               | 
               | Meanwhile in the real world: "I find myself hanging for
               | long periods of time on borrow checker errors. One of the
               | errors has stopped my progress dead for a week now, I
               | swear it worked a week ago and then Rust decided that a
               | borrow I was doing was no good." [2]
               | 
               | [1] https://news.ycombinator.com/item?id=26929012
               | 
               | [2] https://news.ycombinator.com/item?id=23807574
        
             | flohofwoe wrote:
             | If you don't allocate memory in the first place, Rust also
             | looses a lot of appeal though, because dynamic memory
             | management is often the root cause of memory corruption
             | issues in C and C++ (while working with a upfront defined
             | static memory layout avoids some typical footguns, because
             | of things like stable memory locations throughout the
             | lifetime of the application). I'm aware that Rust also
             | helps to make accessing "static" memory safe, but Rust's
             | safety guarantees are much more important with complex
             | memory management patterns.
        
               | jimktrains2 wrote:
               | Rust also helps prevent many types of concurrency bugs,
               | and the type system is much more expressive than c's or
               | c++'s, which can lead to cleaner code as well as
               | preventing bugs like null references.
        
               | pjmlp wrote:
               | Those exposed via thread access to in memory data, for
               | sure.
               | 
               | However we are slowly moving back into multi-processes as
               | way to avoid in-process security exploits and improve
               | overall application stability.
               | 
               | In these kind of scenarios, there is little that Rust
               | type system can help regarding races in IPC.
        
               | pjmlp wrote:
               | The experience with CVEs in MISRA-C(++) vs Java and Ada
               | embedded deployments, proves that there is still room for
               | improvement by pushing C and C++ completely out of the
               | picture.
        
               | yakubin wrote:
               | While it is true that dynamic memory management in C and
               | C++ is a source of memory corruption issues, the other
               | great source is precisely the reluctance of many C and
               | C++ programmers to use dynamic memory allocation instead
               | of buffers of statically-defined sizes. E.g.
               | 
               | 1. Functions which convert a number to a string, where
               | the target string is a byte buffer passed into the
               | function as a pointer argument. This allows C developers
               | to pass pointers to statically-sized arrays of bytes,
               | which leads to buffer overflows. (Inb4 someone shows up
               | with "but that's the programmer's fault for not using an
               | array that's big enough".) Example: strcpy.
               | 
               | 2. Functions which return pointers to static storage,
               | which makes them thread-unsafe, and even in single-
               | threaded cases is error-prone, when you call one such
               | function multiple times in succession (aliasing).
               | Example: gmtime.
               | 
               | Both cases are very prominent in the standard library.
               | 
               | If instead functions allocated memory dynamically, then
               | they would avoid buffer overflow, because size would be
               | handled exclusively within those functions, allocating as
               | much memory as is needed, no more, no less; instead of
               | forcing the programmer to worry about the size at every
               | call-site. They could also be easily made thread-safe and
               | less error-prone (eliminating aliasing), provided that
               | the allocator is thread-safe.
               | 
               | Another issue with the first case is that it leads to
               | uninitialised variables. Often a pattern of "bool
               | NumToString(int n, char* dst, int size);" is employed
               | instead of "Optional<std::string> NumToString(int n);".
               | This invites the possibility of using "dst" without
               | checking the return value, which says if the conversion
               | succeeded. In this case dst is most likely uninitialised,
               | and definitely incorrect. An "Optional<std::string>"
               | solves both issues.
        
               | flohofwoe wrote:
               | All true, but on the other hand you have much more
               | dangerous (because hidden) memory corruption problems in
               | C++ caused by hidden dynamic memory management in the C++
               | stdlib like iterator invalidation, or dangling
               | std::string_views. And IMHO those cases where C++
               | suddenly pulls the rug from under your references are
               | much more dangerous than working with a static memory
               | layout in C.
               | 
               | The C stdlib string API is rubbish of course (along with
               | most other C stdlib APIs). But at least it's quite simple
               | in C to ignore the stdlib (and use better 3rd-party-libs
               | instead) without loosing important language features.
        
           | marcus_cemes wrote:
           | My experience with Rust is that it tries to squeeze every
           | last drop of performance, but instead of relying on
           | simplicity (C) or unsafety (C++, the reason Mozilla started
           | it was to reduce C++ memory-related bugs), it introduces
           | language complexity (lifetimes, "prove that your code is
           | safe, or fallback to a runtime overhead such as Mutex") and a
           | little-bit of runtime overhead (explicit Option<T>/enum
           | checks). In my mind, it doesn't play in the same league as
           | high(er)-level garbage collected languages. It was made for
           | robust low-level applications, which is why it's being
           | considered for the Linux kernel, and not Lisp.
           | 
           | Concatenating strings in a GC language is easy, you just "+"
           | them together, the cleanup will happen eventually.
           | Concatenating in low-level languages means you have to think
           | about the memory allocation(s) taking place, and the
           | format("{}{}") is a lot more powerful for anything more than
           | just adding two strings. What if you want to prepend
           | "https://"? In GC languages, this might be an extra string
           | being allocated and discarded, in Rust, it's a constant hard-
           | coded into the binary. It just happens that the language has
           | attracted a lot of people who are used to higher-level
           | abstractions due to it's relatively pleasant syntax, type
           | system and abstractions, ecosystem and perfomance.
        
             | otabdeveloper4 wrote:
             | C++ is also 'just "+" them together', and it's not unsafe.
        
               | oijk wrote:
               | Turns out, it is:
               | 
               | https://alexgaynor.net/2019/apr/21/modern-c++-wont-save-
               | us/
        
               | otabdeveloper4 wrote:
               | string_view is a new feature designed on purpose to be
               | unsafe.
        
               | pjmlp wrote:
               | The idea was to copy slices model from memory safe
               | languages, naturally being C++ with C's influence
               | regarding performance trumps safety, string_view couldn't
               | be made to be as safe as the idea they were copying from.
               | 
               | Another example is span, while C#'s Span and Microsoft's
               | gsl::span do bounds checking, ISO C++17 span naturally
               | leaves it to the developer's responsibility.
        
           | galangalalgol wrote:
           | I think rust was targetted at c and c++. Do you like any more
           | recent languages or do they all feel like they went the wrong
           | way? I find Julia interesting, but I haven't delved deep.
        
           | rtoway wrote:
           | I think it's fair to say the compiler does a lot of heavy
           | lifting in Rust
        
         | pansa2 wrote:
         | Reminds me of this cheat sheet [0] for slices in Go, which
         | includes a snippet to delete an element from a slice:
         | a = append(a[:i], a[i+1:]...)
         | 
         | [0] https://github.com/golang/go/wiki/SliceTricks
        
           | LukaD wrote:
           | Wow I forgot how unergonomic go can be. And that example even
           | is a potential memory leak...
        
             | masklinn wrote:
             | > And that example even is a potential memory leak...
             | 
             | Yup more generally a slice is not an actual vector, the
             | backing array lives independently from the slice, meaning
             | if you "reset" a slice                   a = a[:0]
             | 
             | all the elements are still part of the backing array, and
             | thus still referenced.
             | 
             | That's also why write-sharing (which is trivial in Go) is
             | super dangerous e.g.                   a := GetSlice()
             | b := a[:]         a = append(a, 1)         b = append(b, 2)
             | 
             | depending on the size of the backing array associated with
             | slice `GetSlice` returns (aka the cap() of the slice), the
             | last element of `a` might be 1 or 2.
        
               | majewsky wrote:
               | Um, what? My impression was always that the last line
               | would copy the underlying slice since someone has already
               | appended to the original backing array. That's the whole
               | point of why append is not in-place. You get a new slice
               | value back because the extension may have happened by
               | copying data into a new backing array.
        
               | masklinn wrote:
               | > My impression was always that the last line would copy
               | the underlying slice since someone has already appended
               | to the original backing array.
               | 
               | Nope. append only copies if there's no room in the
               | backing array, if there is it will just extend the slice
               | in-place and set the new elements.
               | 
               | So if there's room, a will be extended in-place, with the
               | new element set, then b will be extended in-place,
               | overwriting the element set by a.
               | 
               | Hell, how would append even know that somebody else
               | appended to the backing array? As far is it can see that
               | might as well be leftover garbage.
               | 
               | > You get a new slice value back because the extension
               | may have happened by copying data into a new backing
               | array.
               | 
               | That only happens if the backing array is full aka len()
               | + new > cap().
        
               | majewsky wrote:
               | > how would append even know that somebody else appended
               | to the backing array? As far is it can see that might as
               | well be leftover garbage.
               | 
               | Well, the backing array knows its length and capacity,
               | and the slice knows its length, so it's quite trivial to
               | see that there are elements in the array beyond the
               | length of the slice.
               | 
               | EDIT: I'm shocked and appalled by the fact that you're
               | completely right. https://play.golang.org/p/UZSlBMUlc_E
        
         | rascul wrote:
         | 17 different ways to concatenate strings in rust, with
         | benchmarks:
         | 
         | https://github.com/hoodie/concatenation_benchmarks-rs
        
           | tialaramex wrote:
           | Summary: There are several ways to concatenate strings in
           | Rust. The method listed (using the format! macro) is one of
           | the slowest but also most flexible, it will cheerfully
           | concatenate _any_ things that either are, or present an
           | ability to be expressed as, strings.
           | 
           | The very fastest "method" is to just tell Rust you're quite
           | confident that it will be OK to assume all these strings
           | happen to be contiguous in memory anyway and so it can just
           | go ahead and assemble that memory into one string. Rust won't
           | let you pretend _it_ said this was safe, and sure enough in
           | practice it will almost invariably break, but this is often
           | what equivalent C code is trying for, it genuinely is fast,
           | and if you really are confident you can arrange for the
           | assumption to be true Rust lets you express that in an unsafe
           | block so you get that same performance as C.
        
         | willtim wrote:
         | Whilst it may appear that concatenation of strings is easier in
         | other (more declarative) languages, in practice, understanding
         | the runtime performance/complexity and memory usage/lifetimes
         | is far from trivial. Sure, a more declarative language might
         | still be preferable, but for those who need better control and
         | more ability to reason about how the program with execute, the
         | Rust approach makes sense.
        
           | estebank wrote:
           | To drive the point home on how strings work in Rust and what
           | the pitfalls it addresses are,
           | https://fasterthanli.me/articles/working-with-strings-in-
           | rus...
        
         | richardanaya wrote:
         | Check out https://tourofrust.com
        
         | estebank wrote:
         | To be fair, when doing the naive thing, the compiler will tell
         | you what you need to do and (tersely) explain why it is needed:
         | error[E0369]: cannot add `&str` to `&str`          -->
         | src/main.rs:4:15           |         4 |     let z = x + y;
         | |             - ^ - &str           |             | |
         | |             | `+` cannot be used to concatenate two `&str`
         | strings           |             &str           |         help:
         | `to_owned()` can be used to create an owned `String` from a
         | string reference. String concatenation appends the string on
         | the right to the string on the left and may require
         | reallocation. This requires ownership of the string on the left
         | |         4 |     let z = x.to_owned() + y;           |
         | ^^^^^^^^^^^^
         | 
         | This used not to be the case and tripped people up. some even
         | after being told _why_ it is like this disagree that anyone
         | should care.
         | 
         | Rust is aiming at being performant. The thesis is that having a
         | clarity on the actual behavior of code when reading it helps on
         | this front. Because of this, you need to write more code than
         | you would in other languages, while the upside is that when
         | reading Rust code you can be fairly certain of what the code is
         | doing in the final binary, without any thresholds at play
         | changing the generated code from, for example, monomorphization
         | to dynamic dispatch because you added a method or yet another
         | instance. It's a trade-off. A language that _didn 't_ target
         | the embedded and systems space could take all of Rust and
         | radically simplify it, making it closer in experience and
         | performance to Swift. That doesn't mean that the Rust project
         | _won 't_ attempt to make things easier to use and write. A
         | possible future move could be to make `Cow<str>` be the default
         | string people actually use, with `&str` continuing to be the
         | preferred argument and relegating `String`s to specialized
         | cases, probably mostly data buffers.
        
       | ProNeo wrote:
       | Does something like this exist for Kotlin?
        
       | KingOfCoders wrote:
       | <3 Rust
       | 
       | The only thing I find difficult in Rust is lifetimes of e.g.
       | "String"s in data structures, e.g. in Scala I have:
       | case class CustomerSale(name:String, item:String)
       | 
       | and because of GC I don't have to think about life times, copying
       | etc. - data gets removed after a request or when nothing is used
       | any longer.
       | 
       | In Rust it is                 struct CustomerSale{         name:
       | String,         item: String,       }
       | 
       | which rarely works that simple. I need to think of the life times
       | of name and item, e.g. when I get them from a database, and I
       | don't want to own it, or if I want to copy them or have &str etc.
       | And in web frameworks in Rust there is no notion of request life
       | times which would make things much easier.
        
         | pitaj wrote:
         | If you use String which is heap allocated, you don't really
         | need to worry about lifetimes.
         | 
         | &str on the other hand
        
           | KingOfCoders wrote:
           | Yes, String was an oversimplification, String means I either
           | own or copy it, both things I usually don't want to do, so I
           | end up - as you've said - with &str.
        
             | phaylon wrote:
             | You might want to look into using `Arc<str>`. It's an
             | immutable string slice that can have many owners and will
             | be dropped once the last one is done with it.
        
               | KingOfCoders wrote:
               | Thanks, I will try that - fearing my code is littered
               | with Arc ;-) - but sarcasm aside, thanks.
        
               | phaylon wrote:
               | I usually just have a `type Text = std::sync::Arc<str>`
               | at the root, specifically during prototyping :)
        
               | rmdashrfstar wrote:
               | How does this differ from just sharing &str?
        
               | phaylon wrote:
               | A `&str` is a borrow of the string slice data, and has
               | borrowing semantics. An `std::sync::Arc<str>` on the
               | other hand isn't borrowed but has shared ownership and is
               | atomically reference counted.
               | 
               | Does that answer the question? Could you expand on what
               | you mean by sharing `&str`?
        
       | ajross wrote:
       | "Cheat Sheet" prints as a 64 page PDF. Yeah, that sounds about
       | right.
        
       | alpaca128 wrote:
       | My favorite is still the Rust Container Cheat Sheet[0] from the
       | Rust Performance Book[1]. Great overview on just one page -
       | that's what I call a cheat sheet.
       | 
       | Edit: Also recommended: The Little Book of Rust Macros[2]
       | 
       | [0] https://docs.google.com/presentation/d/1q-c7UAyrUlM-
       | eZyTo1pd...
       | 
       | [1] https://nnethercote.github.io/perf-book/title-page.html
       | 
       | [2] https://veykril.github.io/tlborm/introduction.html
        
         | raphlinus wrote:
         | Thanks, appreciate the shout-out.
         | 
         | I'll give a little background how that came about. At the time,
         | I was promoting the use of Rust within Fuchsia. The team was
         | largely composed of badass C++ programmers, who of course were
         | very familiar with reasoning about programs in terms of exactly
         | how things were laid out in memory. The official Rust
         | documentation was at a more abstract level with respect to a
         | lot of this stuff, in many case careful not to overspecify
         | because a lot of the details of memory layout are not normative
         | and may in fact change. In explaining the most common and
         | important Rust data types, I found these kinds of diagrams
         | useful, and it grew into the page you see there.
        
         | est31 wrote:
         | Another container cheat sheet:
         | https://i.redd.it/220xo2f6wci51.png
         | 
         | https://oldreddit.com/r/rust/comments/idwlqu/rust_memory_con...
        
           | alpaca128 wrote:
           | Thanks, that looks helpful too. Note your second link seems
           | to be missing a dot between "old" and "reddit".
        
             | est31 wrote:
             | > Note your second link seems to be missing a dot between
             | "old" and "reddit".
             | 
             | Oh indeed. I visited the link while logged in, but to share
             | links I make the pre-redesign explicit. I guess a mistake
             | occured during that. Can't edit the post any more, but the
             | fixed link is:
             | 
             | https://old.reddit.com/r/rust/comments/idwlqu/rust_memory_c
             | o...
        
       | da39a3ee wrote:
       | This is amazing!
       | 
       | But there seems to be something missing: error handling. After
       | using Rust on and off for a couple of years, a thing I still
       | don't understand is how to write idiomatic code which deals with
       | multiple different error types.
        
         | mesaframe wrote:
         | Enums?
        
         | papaf wrote:
         | _I sill don't understand is how to write idiomatic code which
         | deals with multiple different error types._
         | 
         | I am not sure if it is idiomatic but I have been following the
         | guidelines int this article for general error handling:
         | 
         | https://nick.groenen.me/posts/rust-error-handling/
         | 
         | And this article for having context:
         | 
         | https://kazlauskas.me/entries/errors.html
        
           | da39a3ee wrote:
           | Thank you. (I'd come across the first but not the second). It
           | looks like there is nothing blocking me apart from the need
           | to find a clear morning to which to devote myself to the task
           | of doing error handling properly in my project.
        
         | iudqnolq wrote:
         | My sibling posts a great general overview, and they make a good
         | point about not biasing specific libraries. When I was starting
         | out in Rust, however, I found value in just doing what everyone
         | else was doing and then deciding if I wanted to do things my
         | own way once I was confident I could do the popular way. In
         | that vein, here's doing error handling like a lemming.
         | 
         | Library: thiserror
         | 
         | Application: anyhow (or eyre if you want the same thing but
         | with fancy colors and nicer backtraces).
         | 
         | Make sure you #[from] to derive From a lot. The value is in
         | chains. Here's an example I just wrote
         | #[derive(thiserror::Error)]         enum LoadError {
         | #[error("Failed to parse")]             Parse(#[from]
         | ParseError),             #[error("Failed to acquire lock")]
         | Lock(#[from] file_lock::AcquireError),             #[error("IO
         | error")             Io(#[from] std::io::Error),         }
         | 
         | ParseError and AcquireError are defined in a similar style.
         | 
         | Then I have a function                   fn load(...) ->
         | Result<..., LoadError> {             let lock =
         | FileLock::acquire(...)?;             let parsed = parse(...)?;
         | Ok(parsed)         }
         | 
         | The magic is that ? calls .into on the error, which uses a From
         | implemention to convert the error into the error type the
         | function returns. This means the ParseError gets converted into
         | a LoadError::Parse, for example.
         | 
         | Now we've got that nice semantic cause chain, we use anyhow to
         | display it.                   fn main() -> anyhow:: Result<()>
         | {             let index = load(...)?;             // ...
         | Ok(())         }
         | 
         | If index fails I'll get something like this printed:
         | Error: Failed to acquire lock              Caused by:
         | Failed to open lock             No such file or directory (OS
         | error 2)
         | 
         | It feels like a superpower to have a high-level function fail
         | and see the massive list of errors all the way down to the root
         | cause.
        
         | davidhyde wrote:
         | If you are writing your own library then your best bet is to
         | define your own Error enum in each module. Below each Error
         | enum write a bunch of From trait implementations to convert
         | errors into a custom variant in your Error enum. This allows
         | you to use the "?" operator to "bubble" errors up through your
         | library and build some sort of useful printable error tree.
         | Don't used boxed errors and don't use std:io:Error for your own
         | custom purposes. Doing things this way will mean that consumers
         | of your library will not be bound to use some error handling
         | library that they may not want to use. It is also very fast.
         | 
         | If this is not a library then feel free to use any number of
         | error handling helper libraries out there. It would be unfair
         | to single one of them out. However, once I got the hang of
         | writing my on custom errors I ended up using them for my binary
         | applications too.
        
           | da39a3ee wrote:
           | Thanks, that's very helpful and may well be the encouragement
           | I apparently needed! It is a standalone CLI application but
           | it might want to offer a library in the future. And in any
           | case I am attracted to following your suggestion regarding
           | custom enums, partly because it seems more likely to dispel
           | my lack of understanding of the topic.
        
             | davidhyde wrote:
             | Cheers! It's a daunting topic with many different ways of
             | doing things and it is difficult to find reading material
             | on the topic of idiomatic error handling. I just read
             | through a lot of popular code bases and picked out what I
             | liked. I found that using custom errors was the simplest
             | approach for me albeit a little verbose.
        
           | shepmaster wrote:
           | > If you are writing your own library then your best bet is
           | to define your own Error enum in each module
           | 
           | This is exactly the path that my SNAFU library takes. It's a
           | procedural macro attached to an enum that generates the From,
           | Display and Error traits for you.
           | 
           | This way, consumers of your code don't need to know anything
           | beyond the fact that you implement Error and any inherent
           | public methods or traits you choose to expose. The
           | implementation provided by SNAFU is internal to your code.
           | 
           | https://docs.rs/snafu/0.6.10/snafu/
        
       | gilnaa wrote:
       | Great work!
       | 
       | "Allocate T bytes on stack bound as x" may sound a bit like T is
       | a size instead of a type.
        
       | HugoDaniel wrote:
       | Pretty cool! Is there something like this for other languages? I
       | know that Khronos publishes this pdf for WebGL*
       | 
       | * https://www.khronos.org/files/webgl20-reference-guide.pdf
        
         | dotslashroot wrote:
         | you can look at this: https://overapi.com/#more
        
       | truth_seeker wrote:
       | Thanks. One of the best cheatsheets I have even seen.
        
       | optimalsolver wrote:
       | Any recommendations for a good C++ cheat sheet?
        
       | dotslashroot wrote:
       | Some people have asked the cheatsheet for other languages. you
       | can look at this: https://overapi.com/#more
        
       | golergka wrote:
       | Similar cheatsheet for most of modern languages:
       | http://learnxinyminutes.com
        
       ___________________________________________________________________
       (page generated 2021-04-25 23:02 UTC)