894 karma · joined October 9, 2013
There are two variants of very similar code. Both do the same thing, both are readable and maintainable. The difference is not primarily in performance, it's in quality of craft.
- Proper DB design first. You should be able to remove the ORM and DB should still function as intended. This means application-side cascade operations or application-side inheritance is banned.
- No entities with magical collections pointing to each other. In other words, no n to n relations handled by ORM layer. Create in-between table, for gods sake. Otherwise it becomes incredibly confusing and barely maintainable.
- Prefer fetching data in a way that does not populate collections. In other words, fetch the most fine-grained entity and join related data. Best if you craft special record entities to fetch data into (easy with EF or Doctrine).
- Most ORMs allow you to inspect what kind of queries you create. Use it as query building tool. Inspect queries often, don't do insane join chains and other silly stuff.
I would use ORM in one kind of app: where I would work with data that shares records that might need to be either inserted or updated, and there is several nesting levels of this kind of fun. You know, you need to either insert or update entity, if it exists, you should update, and then assign related entities to it, if it does not, then you should insert, and assign related entities to the newly created id. The ORM can easily deal with that, and on top of that it can do efficient batched queries, which would be really annoying and error-prone to hand-craft.
If the app does not require this kind of database with these kind of relations, I would not use ORM.
What I have learned over the years is that the only way to properly use ORM is as a fancy query tool. Build the query, fetch/update data, MOVE THE DATA to separate business objects. Don't leave ORM entities shared across the sea of objects!
Phew, thanks, I got that off my chest.
Wow. It's like teaching C++ and starting from SFINAE. Or C# and starting from type parameter constraints.
Please think of a real-world examples when teaching stuff. I am very eager to see the program a beginner would need to write that requires: 1) references in a struct; 2) 3 separate lifetime parameters for the same struct.
If's fun little article, but is a bit short-sighted. Probably because otherwise it would not work.
Say, you have several resources that must be cleaned up in certain order. In C, you would just call the appropriate free functions. When abstracting over that with rust, you have to make an annoying choice:
Do what's in C, don't implement automatic Drop, but mark your functions unsafe. You get leanest zero-cost implementation, it is straightforward to understand, but needs additional maintenance care to prevent bugs.
Wrap resources in unsafe structs that have automatic drop (when goes out of scope). IMO this is a terrible choice, because the maintainer suddenly has to know about which structures have this unsafe cleanup going on. Simple scopes suddenly matter, order of items suddenly matter, it's a mess.
Use reference-counting wrappers to track usage and drop the items when they are no longer in use. Most libraries do this, but it's no longer the leanest possible API.
There might be another choice, to achieve both zero-cost execution and correct cleanup using metaprogramming, be it macros, generics, or both. That's exactly what I fear the most.
There is no reason why you can't write simple code in Rust. Well, except that it's tempting to over complicate things. Start using traits and generics everywhere, and you will enter meta-programming in generics hell. Soon after, you will start demanding new compiler features to survive there.
The easiest optimization strategy is just to load it all up into the memory. Compare that to other strategy like catching partial file data, and it's obvious why the simpler solution is often chosen.
Another example that comes to mind is vector art vs baked art. You can render nice icons as vector art. Or you could ship perfect baked icons for all possible size variations. There are clear trade-offs here. One of them wastes more CPU cycles, and another one wastes storage space and download time.
I write code with GPT every day for almost a year now, and it helped me greatly to kickstart code that was hard in the past, namely, audio synthesis, vulkan rendering, and other things that were hard to approach. But it's very clear at this point that it shall not be trusted, because it's like a coat of a nice paint put on top of all the clever or dumb stuff that exists on the internet. You never know which one you get, but sure it will be worded convincingly.
Second, moves by default. They make building wrappers that depend on creation and destruction of a value much easier. They can track various things: memory usage, threads, temporary pointers, or whatever else. Unlike unique_ptr, they are on stack and part of the type system.