Comment by nitros

2 days ago

> But it turns out there's all sorts of delightful data structures that you can only express with unsafe in Rust. Intrusive doubly linked lists are the coolest thing ever.

I don't mean this to be a gotcha, but I think it's important to say that we can have these in rust too! The only difference is that the first thing we have to talk about is you the user can go about using an intrusive collection correctly. I love the cordyceps crate for this. If you want to be able to use your type as a linked list node, you must implement a Linked trait, the documentation of which clearly explains how to use it safely: https://docs.rs/cordyceps/latest/cordyceps/trait.Linked.html

I think this crate description encapsulates what's difficult about unsafe rust, which is how unergonomic pointers are. Like why do I need to use `addr_of_mut!`? I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`. I feel like I'm juggling way more concepts, which to be fair helps with safety, but it can obscure the algorithm itself.

Does cordyceps have a derive macro? I can imagine that helps a lot with correct implemention, though when it comes to linked lists I can see people wanting to do it themselves.

  • > Like why do I need to use `addr_of_mut!`?

    As of Rust 1.82.0 [0] you no longer need to!

    > I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`.

    The addr_of_mut docs [1] give a pretty decent explanation of its reason for existence; in short, it lets you get a pointer to something without needing to create potentially-invalid intermediate references. It (and &raw) probably aren't going to be needed if all you need is a &mut, though.

    > Does cordyceps have a derive macro?

    Doesn't appear to from a quick glance, and given what's in the safety section of the docs [2] it'd probably need to be marked unsafe [3]. Unsafe attributes are new to Rust 2024, though, so that might be a bit new.

    [0]: https://blog.rust-lang.org/2024/10/17/Rust-1.82.0/#native-sy...

    [1]: https://doc.rust-lang.org/std/ptr/macro.addr_of_mut.html

    [2]: https://docs.rs/cordyceps/latest/cordyceps/trait.Linked.html...

    [3]: https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-att...

    • > > Like why do I need to use `addr_of_mut!`?

      > As of Rust 1.82.0 [0] you no longer need to!

      That's true but misleading. I'm pretty sure the question was why a normal reference is bad. You don't need `addr_of_mut!()`, but you do need `&raw mut`.

      1 reply →

    • Interesting. I stopped working with Rust about two years ago, so it looks like there's been a lot more polish in these areas.

  • > what's difficult about unsafe rust, which is how unergonomic pointers are. Like why do I need to use `addr_of_mut!`?

    Things are slowly getting better here! '&raw'/'&raw mut' reference operators were stabilized a couple years ago.

    Another ergonomic improvement in the pipeline is a better way to access fields behind pointers, something like C's -> operator. This is taking some time to design because there are things other than raw pointers that would benefit from a generalized field projection mechanism, like Pin, NonNull, and (potentially user-defined) smart pointer types.

  • > Like why do I need to use `addr_of_mut!`? I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`.

    That documentation appears to be out of date. The `addr_of_mut` macro was elevated to a first-class language feature, `&raw mut` (there's also a corresponding `&raw` operator). These two operators differ from the usual `&mut` and `&` in that they create raw pointers rather than references; prior to the introduction of this feature (or the aforementioned macros that served as precursors) to create a raw pointer you might need to have done a cast like `&foo as *const` in order to create a raw pointer by casting from a reference, but this could have safety consequences if the temporary reference was to an invalid object. Therefore `&raw` and `&raw mut` were introduced to create raw pointers directly without introducing an intermediate reference, which was arguably the most subtle footgun in unsafe Rust for a few years, and it's nice that it's now addressed.