by rck on 1/18/23, 3:14 PM with 170 comments
by anderskaseorg on 1/18/23, 5:03 PM
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
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
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
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
by friedman23 on 1/18/23, 6:07 PM
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
by kris-nova on 1/18/23, 5:26 PM
by linhns on 1/18/23, 4:57 PM
by ElderKorihor on 1/18/23, 10:19 PM
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
by jasonjmcghee on 1/18/23, 4:34 PM
by atemerev on 1/18/23, 6:55 PM
by lesuorac on 1/18/23, 4:28 PM
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
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
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
> 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
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
by lakomen on 1/18/23, 7:49 PM
by joshfee on 1/18/23, 5:32 PM
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
by cormacrelf on 1/18/23, 4:27 PM
by badrequest on 1/18/23, 6:07 PM