The thing with OOP, particularly how it’s used in GCed languages, is that it’s all about handing references out to wherever and then dealing with the complexity of not knowing who has access to your fields via getters & setters, or by cloning memory whenever it’s modified in asynchronous code.
Rust has quite the opposite mindset. It’s all about tracking where references go. It pushes your code to be very tree-shaped, i.e. references typically¹ only exist between a function and the functions it calls underneath. This is what allows asynchronous code to be safe in Rust, and I would also argue that the tree shape makes code easier to understand, too.
But yeah, some of the patterns you might know from OOP will not work in Rust for that reason. You will likely need to get into a different mindset over time.
Also just in case: We are talking OOP in the sense of the paradigm, i.e. object-oriented.
Just using objects, i.e. data with associated functions/methods, that works completely normal in Rust.
¹) If you genuinely need references that reach outside the tree shape, which is mostly going to be the case, if you work with multiple threads, then you can do so by wrapping your data structures in Arc<Mutex<_>> or similar. But yeah, when learning, you should try to solve your problems without these. Most programs don’t need them.
This is basically where my learning took me. I had to develop this notion that there was a preferred directionality to ownership and data flow, like “grain” in a piece of wood. Everything is easier if you go with the grain. “Tree-shaped” works too, since it basically is the call graph of a (single threaded) program.
The point where I realized all this was when I tried to do a very Python/JS-brained thing: return a closure from a function. The moment you try to “curry” values into the closure, you have to “move” them to solve for ownership, lest you bring timelines into the picture. Which isn’t always what you want in a generic and reusable function. And sure enough, the standard lib and other popular libraries want you to pass a closure to functions instead.
The thing with OOP, particularly how it’s used in GCed languages, is that it’s all about handing references out to wherever and then dealing with the complexity of not knowing who has access to your fields via getters & setters, or by cloning memory whenever it’s modified in asynchronous code.
Rust has quite the opposite mindset. It’s all about tracking where references go. It pushes your code to be very tree-shaped, i.e. references typically¹ only exist between a function and the functions it calls underneath. This is what allows asynchronous code to be safe in Rust, and I would also argue that the tree shape makes code easier to understand, too.
But yeah, some of the patterns you might know from OOP will not work in Rust for that reason. You will likely need to get into a different mindset over time.
Also just in case: We are talking OOP in the sense of the paradigm, i.e. object-oriented.
Just using objects, i.e. data with associated functions/methods, that works completely normal in Rust.
¹) If you genuinely need references that reach outside the tree shape, which is mostly going to be the case, if you work with multiple threads, then you can do so by wrapping your data structures in
Arc<Mutex<_>>
or similar. But yeah, when learning, you should try to solve your problems without these. Most programs don’t need them.This is basically where my learning took me. I had to develop this notion that there was a preferred directionality to ownership and data flow, like “grain” in a piece of wood. Everything is easier if you go with the grain. “Tree-shaped” works too, since it basically is the call graph of a (single threaded) program.
The point where I realized all this was when I tried to do a very Python/JS-brained thing: return a closure from a function. The moment you try to “curry” values into the closure, you have to “move” them to solve for ownership, lest you bring timelines into the picture. Which isn’t always what you want in a generic and reusable function. And sure enough, the standard lib and other popular libraries want you to pass a closure to functions instead.