[HN Gopher] How Not to Learn Rust
       ___________________________________________________________________
        
       How Not to Learn Rust
        
       Author : gtirloni
       Score  : 23 points
       Date   : 2021-12-16 15:30 UTC (7 hours ago)
        
 (HTM) web link (dystroy.org)
 (TXT) w3m dump (dystroy.org)
        
       | bko wrote:
       | > But at some point in Rust, and it may come soon, you'll
       | encounter a higher step and if you don't fight it with
       | concentration and dedication, you risk not overcome it.
       | 
       | Can any rustaceans give me an example of a "higher step" in rust
       | would be?
        
         | dystroy wrote:
         | At some point, maybe not immediately, depending on what you're
         | building, you'll have the borrow checker tell you that no, you
         | can't borrow there, or that some construct that you feel safe
         | isn't possible. And it will probably come with a big lot of
         | such errors and you'll have to look for another design pattern.
         | 
         | Until it clicks and starts to be natural, there might be a
         | quite rough period.
         | 
         | Until you know the essential traits, how to find them, and
         | you're used to clauses on generics, you may also have some
         | difficulties making sense of some generic code or making your
         | own generic code work.
        
           | stouset wrote:
           | To expand on this, I think one of the biggest challenges of
           | Rust right now is that the borrow checker and compiler
           | enforce constraints at a "tree"-level that are indicative of
           | "forest"-level design issues with your code. Once you learn
           | to construct forests in a way that harmonizes well with these
           | constraints, you'll very rarely run into these kinds of tree-
           | level issues.
           | 
           | But there needs to be more in the way of education that helps
           | you understand the types of designs that the compiler is
           | trying to push you towards.
           | 
           | You _can_ generally get past things by simply `.unwrap()`ing
           | and `.clone()`ing your way past compiler issues, but reaching
           | for them too often prevents you from ever learning those
           | (important) lessons given the current lack of better
           | alternatives to learning them.
        
           | bko wrote:
           | From the article:
           | 
           | > So before you dive, have an overview of the book (or
           | books). Maybe don't read it fully, but make sure to have read
           | all titles so that you know when to come back to it later.
           | 
           | Could someone familiar with Solana smart contract development
           | highlight which titles from 'the book' are most relevant? I
           | imagine developing smart contracts is much more scoped down
           | than most rust projects.
        
           | ithkuil wrote:
           | I've passed that hill behind me and I rarely have to fight
           | the borrow checker. If you're familiar with the ownership
           | model and learn the few scoping quirks that's not that hard.
           | 
           | What I find way harder to cope with is the gazillions of type
           | parameters with gazillions of guards, send, sync, clone or
           | not clone, then impl Foo for Bar vs impl Foo for Arc<Bar> etc
           | etc; and tons of features in cargo crates, also in private
           | workspace projects, .... And the compiler slowness oh man the
           | compiler slowness.
           | 
           | EDIT: my life improved a bit since I learned about the cargo
           | hakari plugin
        
         | jph wrote:
         | Some steps that newcomers ask me about involve memory
         | management, the borrow checker, and sharing data, such as Rc,
         | Arc, dyn, cell.
         | 
         | https://doc.rust-lang.org/std/rc/struct.Rc.html
         | 
         | https://doc.rust-lang.org/rust-by-example/std/arc.html
         | 
         | https://doc.rust-lang.org/std/keyword.dyn.html
         | 
         | https://doc.rust-lang.org/std/cell/
        
         | duped wrote:
         | Here's a case I ran into.
         | 
         | I have a `Span` type and it can act like `RangeInclusive`. I
         | want to index into a `&str` with it. This looks something like
         | this :                   use std::ops::Index;         pub
         | struct Span {             start:usize,             end:usize,
         | }                  impl<'a> Index<Span> for &'a str {
         | type Output = str;             fn index(&self, span:Span) ->
         | &Self::Output {                 &self[span.start..=span.end]
         | }         }
         | 
         | (NB4 RangeInclusive, in this example, Span is almost identical
         | to RangeInclusive, in practice it was not)
         | 
         | Playground: https://play.rust-
         | lang.org/?version=stable&mode=debug&editio...
         | 
         | This gives you a cryptic error :
         | error[E0308]: mismatched types           --> src/lib.rs:10:19
         | |         10 |             &self[span.start..=span.end]
         | |                   ^^^^^^^^^^^^^^^^^^^^^ expected struct
         | `Span`, found struct `RangeInclusive`            |            =
         | note: expected struct `Span`                       found struct
         | `RangeInclusive<usize>`                  For more information
         | about this error, try `rustc --explain E0308
         | 
         | This is weird, because you can definitely index into a `&str`
         | with `RangeInclusive`.
         | 
         | What you actually need to do is implement it for `str` instead
         | of `&'a str`. This is a little peculiarity with how the `str`
         | type works (to date, this is the only time I've ever needed the
         | raw `str` type and not &str! it almost never is required) and
         | the generic implementation for Index<I> for str combined with
         | how Deref works.
         | 
         | It's a small one line change to fix the code, but it's not
         | obvious from the error message how to do it, and a bit tricky
         | to solve unless you knew a little bit more about how Rust's
         | operators get parsed/implemented.
         | 
         | In my experience this is a common class of problems when you
         | deal with generic and trait programming in Rust. It's
         | ultimately the front end to an advanced constraint solver, and
         | due to the explosion of complexity when you start making
         | generic things more/less generic you will hit the wall with
         | weirdness and need to understand more about the type system
         | rather than letting the type system prevent you from doing
         | stupid things.
         | 
         | Frankly the memory management issue is not a problem once you
         | buy into ownership.
        
         | ridiculous_fish wrote:
         | I sheepishly admit to repeatedly trying and failing to overcome
         | modules.
        
       | zcmack wrote:
       | this was super effective at convincing me not to learn rust
        
       | lumost wrote:
       | The linked list debate should really be improved. From a
       | practical perspective there are many reasons why one may wish to
       | model a cyclic, linked datastructure.
       | 
       | Simple examples:
       | 
       | You have a social networking site that you are prototyping. You
       | choose to start your data model with a simple Person struct which
       | contains a list of Person called friends.
       | 
       | You have an e-commerce site and choose a data model which has a
       | User struct which contains a Cart reference, and a Cart struct
       | containing a User reference for its owning user.
       | 
       | You have any problem which involves a graph data-structure.
       | 
       | In Rust the best way to work around these issues is to have
       | explicit lookup's by id rather than direct traversable
       | references, or use unsafe. There are no other mechanisms. It's a
       | missing feature that really should be highlighted early in the
       | docs or even as a compiler error such as "cyclic ownership
       | detected in Struct X -> References Y -> References Z in
       | main.rs#128 consider removing the cycle or using Rc, Arena, or
       | unsafe allocations"
       | 
       | As it stands if you create a reference cycle in rust you'll get a
       | ton of compiler errors that force you to learn the rest of the
       | ownership rules just to find out that this is something you can
       | never do.
        
         | nauticacom wrote:
         | I don't enjoy writing Rust but its perspective on this is at
         | least internally consistent. What are the alternatives for
         | managing cyclic, linked data structures in other languages?
         | 
         | In languages with manual memory management you can do it, but
         | it's incredibly easy to mess up. You either have to maintain
         | the entire mental model of who owns what data and what data has
         | been initialized in your head or write it down and risk that
         | becoming out of date.
         | 
         | In languages with a GC, the implementors have assumed this
         | complexity for you. Depending on what style of GC your language
         | uses the exact strategy will be different, but on a GC is a
         | comparatively complex piece of code, especially one which
         | handles reference cycles well like we're talking about here.
         | 
         | Either way, the complexity is there somewhere. If you manage
         | memory yourself, you deal with the complexity yourself in a
         | hard-to-debug way. If you can accept the tradeoffs of a GC,
         | then the complexity is abstracted behind the GC. Because Rust
         | is designed for use-cases where you can't accept the tradeoffs
         | of a GC, it _has_ to surface that inherent complexity
         | somewhere. It decides to surface it with ownership semantics,
         | which at least make the rules you 're following in manual
         | languages explicit and (largely) unbreakable.
         | 
         | Yeah it's complex, but the underlying problem is complex. I can
         | use GC languages, so I do, but you'd be no better off in C.
        
         | dystroy wrote:
         | What I meant in this article is that designing the ways to deal
         | with graph-like structures should be done after the ownership
         | model has been understood. If you start your first program
         | directly on this kind of problem, you'll be only fighting the
         | borrow checker and not really enjoying Rust.
         | 
         | I should probably have taken my own experience with broot
         | rather than the linked list example (which hurts many fans of
         | its theoretical beauty).
        
       | jph wrote:
       | > trust the input of your fellow functions, and don't lose too
       | much time looking for assertions to add.
       | 
       | Rust makes it so easy to add assertions that it's well worth
       | doing IMHO, and the assertions make it easy to detect corner
       | cases such as with lengths, collections, comparisons, unexpected
       | inputs, etc.
       | 
       | Rust comes with a handful of assertions, and there are crates
       | that provide many more, as well as runtime equivalents for
       | function input validation. I wrote the "assertables" crate to
       | help with these: https://github.com/sixarm/assertables-rust-crate
        
         | dystroy wrote:
         | IMO those assertions are more useful in tests (which Rust make
         | easy) than to protect your functions from inconsistent inputs.
        
         | duped wrote:
         | Returning a `Result<>` for corner cases is so much better than
         | random asserts because it forces callers to handle them.
         | 
         | Personally I reject anything that looks like `assert(...)`
         | found outside of tests and ask they be replaced with a proper
         | error or the idiomatic way to express a fatal/unrecoverable
         | error in your language. In Rust that's `unreachable!` and
         | `panic!`, not `assert!`.
        
           | trevyn wrote:
           | I think an appropriate assert_eq! is more idiomatic than an
           | equivalent panic!, since it automatically reports the
           | offending value in the panic message.
        
       ___________________________________________________________________
       (page generated 2021-12-16 23:02 UTC)