from Hacker News

Rust concepts I wish I learned earlier

by rck on 1/18/23, 3:14 PM with 170 comments

  • by anderskaseorg on 1/18/23, 5:03 PM

    > With the code above, Rust does not know whether we want to clone Arc or the actual inner value, so the code above will fail.

    This is incorrect. The code works fine as written; Rust will let you write .clone() and it will clone the Arc (without cloning the inner value). More generally, methods on a wrapper type are searched before methods found via auto-deref. It’s often considered better style to write Arc::clone(…), but that’s for human readability, not the compiler. There’s a Clippy lint for it (https://rust-lang.github.io/rust-clippy/master/#clone_on_ref...) but it’s turned off by default.

  • by kris-s on 1/18/23, 4:38 PM

    The author mentions the following:

        fn add(x: Option<i32>, y: Option<i32>) -> Option<i32> {
            if x.is_none() || y.is_none() {
                return None;
            }
            return Some(x.unwrap() + y.unwrap());
        }
        The above looks kind of clunky because of the none checks it needs to perform, and it also sucks that we have to extract values out of both options and construct a new option out of that. However, we can much better than this thanks to Option’s special properties! Here’s what we could do
    
        fn add(x: Option<i32>, y: Option<i32>) -> Option<i32> {
            x.zip(y).map(|(a, b)| a+b)
        }
    
    Do folks really prefer the latter example? The first one is so clear to me and the second looks inscrutable.
  • by perrygeo on 1/18/23, 5:07 PM

    Nice article, but I'm not sure I like using the Deref trait just to make code "cleaner" - it does the opposite IMO, making it harder to understand what's going on.

    Deref is convenient for builtin "container" types where the only thing you'll ever do is access the singular value inside it. But sprinkling it everywhere can get confusing ("why are we assigning a &str to a struct here?")

  • by woodruffw on 1/18/23, 4:31 PM

    Nice list!

    This is a nitpick on my part, but this part on PhantomData:

    > Tells the compiler that Foo owns T, despite only having a raw pointer to it. This is helpful for applications that need to deal with raw pointers and use unsafe Rust.

    ...isn't quite right: `_marker: marker::PhantomData<&'a T>,` doesn't tell the compiler that `Foo` owns `T`, but that instances of `Foo` can't outlive its `bar` member. `bar` is in turn borrowed, since a pointer is "just" an unchecked reference.

    You can see this in the playground[1]: `foo1` and `foo2` have the same borrowed `bar` member, as evidenced by the address being identical (and the borrow checker being happy).

    Edit: What I've written above isn't completely correct either, since the `PhantomData` member doesn't bind any particular member, only a lifetime.

    [1]: https://play.rust-lang.org/?version=stable&mode=debug&editio...

  • by geewee on 1/18/23, 4:08 PM

    Having worked with Rust for a little while (about a year) none of this stuff is particularly esoteric. However it is a great list of things that are good to know, but you don't necessarily need all that often (or learn from the official rust book)
  • by friedman23 on 1/18/23, 6:07 PM

    I'll share an architectural pattern from Rust that is pretty ubiquitous but not really mentioned in the official books.

    Skip the whole weak rc nonsense and jump directly to using an allocator (like slotmap) when dealing with cyclic data structures. Wrap the allocator in a a struct to allow for easy access and all the difficulty from rust cyclic datastructures disappears.

  • by ihnorton on 1/18/23, 7:41 PM

        Really annoyed by the borrow checker? Use immutable data structures
        ... This is especially helpful when you need to write pure code similar to that seen in Haskell, OCaml, or other languages.
    
    Are there any tutorials or books which take an immutable-first approach like this? Building familiarity with a functional subset of the language, before introducing borrowing and mutability, might reduce some of the learning curve.

    I suspect Rust does not implement as many FP-oriented optimizations as GHC, so this approach might hit performance dropoffs earlier. But it should still be more than fast enough for learning/toy datasets.

  • by puffoflogic on 1/18/23, 4:34 PM

    Author has confused the drop functions. `Drop::drop` and `mem::drop` have nothing to do with each other.
  • by kris-nova on 1/18/23, 5:26 PM

    Great read — the author should feel proud. This made my morning.
  • by linhns on 1/18/23, 4:57 PM

    This is somewhat off-topic but it's nice to see someone using Zola for their own blog, awesome SSG built on Rust!
  • by ElderKorihor on 1/18/23, 10:19 PM

    > Use impl types as arguments rather than generic constraints when you can

    Eep. No. At least, not in anything public. Universal impl trait arguments can't be turbofished, so you're placing an unnecessary constraint on your caller.

  • by cassepipe on 1/18/23, 8:23 PM

    One thing I wish Rust and C++ had and that I have only seen in Carbon is pass-by-reference by default + an explicit syntax to pass by copy + for rust, some syntax to mark that we are passing a mutable/exclusive reference.
  • by jasonjmcghee on 1/18/23, 4:34 PM

    Small note- at least one of the links on mobile does not respect the text column width of the page, so the actual page width is wider than the text and horizontally scrollable.
  • by atemerev on 1/18/23, 6:55 PM

    Yes, most of my code deals with graph-like data structures. It is _really_ hard to understand how to write this in Rust. Just doesn't fit in my head.
  • by lesuorac on 1/18/23, 4:28 PM

    Well not in the article but I saw somebody doing a guard clause that I started to copy.

    i.e.

    ```

    let min_id = if let Some(id) = foo()? { id } else { return; }

    ...

    let bar = if let Some(bar) = baz()? { bar } else { return; }

    ..

    // vs

    if let Some(id) = foo()? {

    ...

    if let Some(bar) = baz()? {

    ..

    }}

    ```

    It's nice to also do `if let (Some(x), Some(y)) = ...` but sometimes you need the result of `x` to do a few things before you can get `y` (or maybe don't went to evaluate `y` depending on `x`).

    ---

    I like the `where` syntax more than the example formatting.

    ```

    fn x<T>(t: T) where T: Foo {}

    ```

  • by endorphine on 1/18/23, 6:54 PM

    > There are two kinds of references in Rust, a shared reference (also known as a borrow)

    Is that really what they're called? It seems confusing to me: if it's shared (i.e. many people use it at the same time) how can it also be borrowed (i.e. one person has it)?

  • by ww520 on 1/19/23, 9:36 AM

    This is a fantastic list. I've certainly learned something new.

    The comments here are unnecessary negative. People seem to be upset on things that don't look familiar. Don't let the negative comments get to you. Keep up the good work.

  • by avinassh on 1/19/23, 5:41 AM

    In the traits example

    > which tells the compiler “I just want something that implements Meow”.

    The ‘trait Meower’ also implies same, right? If so, why can’t we use that

  • by wizzwizz4 on 1/18/23, 5:30 PM

    > Normally, Rust would complain,

    Not in the example given. There's nothing wrong with creating an Rc<T> loop; the borrow checker doesn't come into the picture.

    > That is, you could mutate the data within an Rc as long as the data is cheap to copy. You can achieve this by wrapping your data within a Rc<Cell<T>>.

    T: Copy is only a bound on the .get() method. You can do this even if the data is expensive to copy, so long as you always swap in a valid representation of T. (I sometimes write Cell<Option<T>>, where it makes sense to replace with a None value.)

    > Embrace unsafe as long as you can prove the soundness of your API,

    In other words: avoid unsafe except as a last-ditch resort.

    > &impl Meower

    Should be impl Meower, if you want the same behaviour as the explicit-generic version.

    > Many tutorials immediately jump to iterating over vectors using the into_iter method

    Out of interest, what tutorials? I've never read one that does that!

    > Instead, there are two other useful methods on iterators

    Methods on many iterables in std. Not on iterators (nor iterables in general).

    > You can wrap the following types with PhantomData and use them in your structs as a way to tell the compiler that your struct is neither Send nor Sync.

    … You're doing it wrong. €30 says your unsafe code is unsound.

    > Embrace the monadic nature of Option and Result types

    Er, maybe? But try boring ol' pattern-matching first. It's usually clearer, outside examples like these ones (specially-chosen to make the helper methods shine). I'd go for if let, in this example – though if the function really just returns None in the failure case, go with the ? operator.

    > For example, writing a custom linked list, or writing structs that use channels, would typically need to implement a custom version of Drop.

    No, you don't typically need to. Rust provides an automatic implementation that probably does what you need already. Custom drop implementations are mainly useful for unsafe code.

    > Really annoyed by the borrow checker? Use immutable data structures

    No. No. Haskell has all sorts of optimisations to make "immutable everything" work, and Rust's "do what I say" nature means none of those can be applied by the compiler. If you want to program this way, pick a better language.

    > Instead, you can define a blanket trait that can make your code a more DRY.

    There are no blanket traits. There are blanket trait implementations, and you've missed that line from your example.

    All in all, this is a good article. There are some good tips in there, but I wouldn't recommend it to a beginner. I would recommend the author revisit it in a few months, and make some improvements: a tutorial designed when you're green, but with the experience from your older self, can be a really useful thing.

  • by 1980phipsi on 1/18/23, 4:32 PM

    The one about exclusive references is good.
  • by lakomen on 1/18/23, 7:49 PM

    God almighty, that language is sl fugly to look at
  • by joshfee on 1/18/23, 5:32 PM

    > there is a lot of excellent Rust content catering to beginners and advanced programmers alike. However, so many of them focus on the explaining the core mechanics of the language and the concept of ownership rather than architecting applications.

    The author then goes on to write an article largely covering the mechanics of the language rather than architecting applications.

  • by antman on 1/18/23, 6:09 PM

    I read this 3-4 times and realized why people stick to python though
  • by cormacrelf on 1/18/23, 4:27 PM

    “Fed up with the borrow checker? Try writing your own immutable linked list.” Yeah, that’ll help!
  • by badrequest on 1/18/23, 6:07 PM

    I love how Rust has so many footguns that you need to read a dozen blogs from no less than a month ago to avoid utter confusion.