Vec of struct inside struct

Hi, I am learning Rust and writing my very first lines of code. I want to create a small particles simulator program, I have this:

#[derive(Debug)]
struct Particle {
    mass: f64,
    interactions: Vec::<&Particle>, // I want to store references to other particles here.
}

fn main() {
    let mut A = Particle {
        mass: 1.,
        interactions: Vec::new(),
    };
    
    let mut B = Particle {
        mass: 2.,
        interactions: Vec::new(),
    };
    
    A.interactions.push(&B);
    B.interactions.push(&A);
    
}

Inside Particle::interactions I want to store a list of other particles. Since I will not modify these particles from there, they should be references to the other particles. I thought this would work, but I get

--> src/main.rs:4:25
  |
4 |     interactions: Vec::<&Particle>, // I want to store references to other particles here.
  |                         ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
2 ~ struct Particle<'a> {
3 |     mass: f64,
4 ~     interactions: Vec::<&'a Particle>, // I want to store references to other particles here.
  |

I tried following the recommendation of the compiler adding the 'a (which I still don't know what it means) but then I get other errors.

How could I achieve having a list of references to other particles inside each particle?

The whole point of Rust, it's goal, it's raison d'etre is precisely these two sentences (one yours, one mine):

You can't.

IOW: You can not do that not because you don't know Rust or because you are writing some wrong sigils in your code, but precisely because Rust is made to prevent creation of such code.

I strongly suspect that you are doing at least half-dozen of errors from the well-known list here.

Rust is incredibly resistant to attempts to write C in Rust, Java in Rust, or even Haskell in Rust.

And trying to write rust program using ideas from other languages is hard. Not impossible, mind you, the determined Real Programmer can write FORTRAN programs in any language, after all, but hard.

In particular phrase I will not modify these particles from there is extremely worrying. Simply because from there is just not done in Rust: either you are in place of program where you can modify something in it's not in modifyable state, instead.

An attempts to try to write code in Rust without reading some books (an official Rust book is fine, but by now there are others) looks like an attempt to fly an arplane using years of experience driving cars: you may achieve… something, but you just wouldn't be able to fly without crashing.

In Rust case this often ends with a strict conviction of newbie that “I'm almost done, I just need to convince compiler to accept my code” where in reality code is not even remotely close to being in the shape that would ever be able to be compiled.

In particular you program have two Particle elements on stack and each one references the other. But, as was already noted, that's “a pile of pointers” type of data structure that Rust compiler is supposed to prevent from existing!

4 Likes

What would be the way of doing this in Rust? The underlying data structure is a kind of graph, which does not look so complicated actually and I would like to implement by myself in order to learn the basics, without just calling some library.

Introduction - Learning Rust With Entirely Too Many Linked Lists This is like the book about the principles of tree data structures in Rust. It mainly talks about lists, but the teachings should be applicable to other kinds of trees as well.

2 Likes

It may not be all that complicated and it may be handled by Rust pretty decently if you would use something like petgraph, but before it would even make sense to talk about it you would need to know about how ownership and borrow system works, how exclusive and shared references work, how enums works, how pattern matching works, how inner mutability works, how generic and traits work and so on.

IOW: before you may even attempt to write something like this you would need to know decent amount of Rust, almost everything that's in the the book

That's why in the article that I have linked an attempt to skip learning basics with the use of some tutorial are mistakes #1 and #2 and an attempt to greate some graph-like data structure is msitake #3.

Graphs are, essentially, the most complicated data structures that may ever exist and to correctly and efficiently work in with the in Rust (without any libraries) you need to know almost all the darkest, most complicated and dangerous corners of Rust.

For a long time they were, quite literally, the most complicated thing that one may ever write in Rust.

They are not the most complicated thing you may decide to create in Rust today simply because async used graph-like structures and some more things on top of that.

You are, essentially, attempting to run a marathon not just before you have learned to walk, but before you have even learned to crawl… in theory that should even be possible, but I know of precisely zero people who managed that feat.

Thanks for your links. I see that Rust seems to be not good for "complicated graph structures", or at least the advantages of Rust cannot be fully exploited with such kind of structures, which I think is unavoidable in the problem I wanted to solve. I will try to think of another way of implementing this.

But graphs are not a tree data structure, are they? Of course too many linked lists talk, in the end, about arbitrary graphs, too, not just about a tree data structures but that's ultimately, a recognition of the fact that a tree data structure sometimes doesn't work and if that actually happens then you need to handle more complicated things, too.

That's really good book, but an attempt to crate something that's we would rather not ever support at all but need to support because real world problems sometimes need it is hardly a healthy way to approach “studying of the basics”.

The Too Many Linked Lists document is good. My suggestion is to instead try implementing a graph with the popular library that is made for this, petgraph. But before any of that, please take @khimru's advice and go through the book.

Use a container (like Vec<Particle>) and store the indices in Particle instead.

3 Likes

That's great advice if the goal was to just create that simulatior, somehow. You may even create it in BASIC or BASH that way!

But the goal was specifically to learn how data structures work in Rust and the “obvious” (for someone who is familiar with how they work in some other language like Java or Python) way to do that is to start with “something simple, but complicated enough to be interesting” (double-linked list is often picked for that reason).

Unfortunately, with Rust, arbitrary graph (or double-linked list) is just beyond that threshold of what's feasible without working good knowledge of Rust.

Solving the problem via use of flat array and indexes in that array is possible, of course, but that would lead to aforementioned FORTRAN program in any language result.

Just FYI: petgraph uses this vec + indices approach for most of it's graph types. :slight_smile:

1 Like

TBF, this really is how graph works in Rust unless you are in to ultra-unsafe land. Without a container, there's no clear owner of the data, you will just leak everything (unless you have some problem specific way to correctly drop nodes).

And you can't really do better in C/C++. You can store the pointer, but you still need a way to release the data. Either you introduce an external storage (like a vector that never reallocate), or have some magic, or just leak everything.

Another alternative is allocate a Vec<Rc<T>> and store links: Vec<Weak<T>> in T. But it's not like you are gaining anything anyway.

(And yes, you can introduce a GC, but that's just a container with extra steps.)

4 Likes

Thank you for your replies. I will try to implement it with two Vec, one for the particles and the other for the interactions. From the physics point of view, the most trivial data structure is the graph, in the sense that is the one most directly resembling physics, which is the only reason I went straight to this approach.

This is too limiting very often. C++ have special data structure which is custom-tailored for such work, std::deque: it's iterator invalidation rules make it possible to play at the very edge of unsafety without triggering UB.

Yes, and that's the issue. In Rust you couldn't play that close to the edge of unsafety without burning. Even in C++ it's hard.

This whole discussion reminds me of old funny tale about Siberian luberjacks:

We once bought a Japanese chainsaw for tough Siberian lumberjacks.
Lumberjacks gathered in a circle and decided to test it.
They started it up and fed it a small tree.
"Vzhik," said the Japanese saw.
"Wow, fuck…" said the lumberjacks.
They fed it a thicker tree.
"Vzh-zh-zhik!" said the saw.
"Wow, fuck!" said the lumberjacks.
They fed it a thick cedar.
"VZH-ZH-ZH-ZH-ZH-ZH-ZH-ZH-ZHIK!!!" said the saw.
"Wow, fuck!!!" exclaimed the lumberjacks.
They fed it an iron bar.
"CRACK!" said the saw.
"Ah, damn!!!" the rugged Siberian lumberjacks said disapprovingly! And then went off to chop wood with their axes...

For some reason the expectations of newbies are always that Rust would, somehow, magically, make it easy to implement something that you were hard pressed to implement in the $LANGUAGE_THAT_THEY_HAVE_USED_BEFORE. But that's not how it works! Rust instead makes 95% (or, sometimes even 99%) of code which you could write easily before bug-free. Then, without the need to spend resources on debugging that mass of code you may concentrate your mental energy on the way to solve that remaining 1% or 5% of the task. And graph algorithms fall into these few percents.

1 Like

Graphs in general are not trees. I interpreted this statement from OP

Inside Particle::interactions I want to store a list of other particles.

To be a tree. But I may have made wrong assumptions.

This:

    A.interactions.push(&B);
    B.interactions.push(&A);

doesn't look like a tree to me. And that was part of the very first message.

In fact, even if we forget Rust struggles and only think about physics… even then it's far from being the simplest possible contructs. The fact that one particles interacts with all other particles is source of enormous complexity and the reason we couldn't calculate anything precisely if there are measly three bodies but situation becomes enormously simpler if one mass is much heavier than two others because… this turns arbitrary graph into a tree!

Sorry, I must have overlooked that.