At my work, we have developped `loupe`, a Rust crate that does precisely that, https://github.com/wasmerio/loupe/. I'm the main author of it.
`loupe` provides the `MemoryUsage` trait; It allows to know the size of a value in bytes, recursively. So it traverses most of the types, and its fields or variants as deep as possible. Hopefully, it tracks already visited values so that it doesn't enter an infinite loupe loop.
We are using it inside Wasmer, a WebAssembly runtime.
I don't think ownership really lets you do anything more than what "sizeof" tells you in C. In particular:
- Some types have dynamic memory allocation (Vec, HashMaps etc...), so those would have to be computed at runtime and can change at any moment.
- Some types have shared ownership (Rc/Arc), so it's unclear how you would measure memory usage then.
- Some types, especially in foreign interfaces, will effectively just hold a pointer to some black box data, you'd need a special API to figure out how much memory it hides. For instance what's the memory usage of a database handle or a JPEG compression library context?
- When you care about memory usage things like fragmentation are usually very important, and the amount of memory used by a given object can be misleading. If you have a string that takes up 12 bytes but it's the only object left in the middle of a 4KiB page, then just counting "12 bytes" for this object is misleading because you have a huge fragmentation overhead.
The only advantage of the borrow checker is that safe Rust forces you to make ownership relationships explicit but not all Rust code is safe and there are many escape hatches that muddy the water (like Rc/Arc mentioned above, but also threads and a few other things).
> - Some types have dynamic memory allocation (Vec, HashMaps etc...), so those would have to be computed at runtime and can change at any moment.
I thought that's exactly the question that was being asked? Something like a htop for a rust program. This could be helpful in improving code efficiency.
> - Some types have shared ownership (Rc/Arc), so it's unclear how you would measure memory usage then.
Same way we measure the memory usage of programs running on systems that support shared libraries and mmapped data. Seeking perfection here is the obstactal; the goal is to attribute memory usage to an object (and preferably, a function call) so that we can improve its characteristics.
> - Some types, especially in foreign interfaces, will effectively just hold a pointer to some black box data, you'd need a special API to figure out how much memory it hides. For instance what's the memory usage of a database handle or a JPEG compression library context?
It's true that if you integrate with external systems, you don't get the benefits of the system you chose to make your home. This doesn't mean you should try to get as much benefit as possible.
> - When you care about memory usage things like fragmentation are usually very important, and the amount of memory used by a given object can be misleading. If you have a string that takes up 12 bytes but it's the only object left in the middle of a 4KiB page, then just counting "12 bytes" for this object is misleading because you have a huge fragmentation overhead.
This is interesting to me, and I don't know much about it. It sounds like it's not an actual limitation to the utility of the tool, but it should certainly guide how it is built and how its results are interpreted.
> The only advantage of the borrow checker is that safe Rust forces you to make ownership relationships explicit but not all Rust code is safe and there are many escape hatches that muddy the water (like Rc/Arc mentioned above, but also threads and a few other things).
In conclusion, I don't think that renders the activity pointless. The fact that hazy information is hazy and requires careful interpretation doesn't make it useless. People find test cases and static type checks useful even though they don't answer the question "is my code correct". And we're all the time relying on half measures that answer parts of the questions here (I once developed a program and used the load averages from uptime to tell me if it was performant enough :/). The pursuit of perfection is the enemy of improvement. It might be that there is some fatal flaw, but I don't think you've mentioned any here.
I don't think the ownership system gives you that directly. But I think you could make a trait with a #[derive] macro which could add up the owned memory of an object. And for complex objects, recursively calculate the owned memory for all of an object's fields.
The Java model ties one object to another. The rust model ties a consumed memory to a function. A debug log output that shows how much memory this function call required, in the same way it shows how much time it uses?
It didn't use ownership, and I don't think that's the paradigm, but I did something like this in D by tagging allocating members with a user defined attribute which counted in bytes the allocated memory the summed it for the whole tree (structure).
Not worth the effort but it can be done in not much code