Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've not really used Rust, and I can certainly appreciate the goal of the language. However this code bothers me, and I can't really articulate why.

    struct Node {
        value: i32,
        children: Vec<Rc<RefCell<Node>>>>,
        parent: Option<Weak<RefCell<Node>>>>, // Weak breaks parent→child cycle
    }
I understand its all boxes inside boxes inside boxes, but as an outsider it looks confusing mixing data type semantics with memory managment semantics.
 help



It bothers me too, maybe for different reasons, as an experienced rustacean. Code like this is a world of pain (in any language, might I add, even though Rust is the only one that shows you why with the syntax).

Internally shared ownership is a huge anti-pattern and rarely achieves what you’re actually trying to do. Graphs like these are much better represented by splitting the topology from the node data, and using indices or keys to form edges instead of (smart) pointers.

You can use pointers, but then the correct choice is actually raw pointers and `unsafe`, with a safe whole-graph-level API for traversal and access. This is what Rust’s own collection types do internally.


Do you have a simple example of how this is done correctly?

I always heard that but comming from Python internal references would be the way to go so it's not natural to me.


Depending on the shape of the graph (arbitrarily connected, DAG, etc.), it just boils down to whatever convenient way you have of storing lists.

Node data in one list. Indices of node parents in another list of the same length. If you need to find children quickly, you can have another list of lists containing children indices.

The key is to consider the node’s position in the list as its ID, and refer to nodes by that index instead of the pointer.

In most cases, this model is both easier to work with and more performant. For example, you can usually get by with 32-bit indices instead of 64-bit pointers, so things are much more likely to be in cache.


Thanks. So you basically make pointers manually with your own separated stack. Wouldn't make sense to have the ability to just allocate anew stact you dedicate to a graph and let the ownership system deals with it for you? Seems like a missing abstraction.

I recently saw a submission here that does that, by essentially implementing GC in Rust. It is not beginner material though. https://kyju.org/blog/tokioconf-2026/

Edit: also the simplest way how to do cyclic structures is to heap-allocate via Box and leak memory. Box::leak This is also mentioned in the linked article.


All these Weak, RC are needed for allocation/deallocation of memory. Having data in list with indexes means you don't have this functionality.

For what it's worth, you can pull the same tricks in any language.

I wouldn't like to work with it, but I wouldn't be too surprised if I encountered a List<Optional<WeakReference<Holder<Node>>>>. A list, containing optionally-present weak references to a holder object (which you might need if you intend to update such an object from a lambda, as those can only take effectively final variables from their parent scope).

I think there's value to the Rust implementation. In many other languages, these wrappers are either non-optional or hidden away with syntax, but they're still present. The Rust approach puts the developer in charge of how data should be exchanged (copied/borrowed/on stack/in heap/etc.). You can write extremely efficient programs that stack very few of those types, with many restrictions on how they ca be passed around to methods, or you can make your life easier at the cost of some performance and clutter.

For clarity, you could do something like this:

    type RVec<T> = Vec<Rc<T>>;
    type RNode = RefCell<Node>;
    
    struct Node {
        value: i32,
        children: RVec<RNode>,
        parent: Option<Weak<RNode>>
    }
That would allow for some much-needed brevity, but it would also fill your code with custom types that others will need to learn about.

Memory _is_ data at the level Rust works. It just has a few abstractions in the stdlib that hide this. And the code you highlighted is taking steps in the "let's add abstraction" direction

I see beginners trip over themselves when they get obsessed with allocation (never mind they're coming from like, Python) but in a language like Rust (or C++) you are writing programs with the intent to control how memory is managed. So it makes a lot of sense to tie memory to the types as a part of their semantics.

It's not without problems, but the idea is less confusing in practice than it seems.


yeah it's too many generic depth...

there ought to be a crate that handles "Vec<Rc<RefCell<..." part (maybe https://crates.io/crates/orx-concurrent-vec ? haven't tried it yet)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: