Hacker News new | ask | show | jobs
by jcelerier 2087 days ago
There's some tradeoff there though. I'm doing most of my dev in C++ and run all the time with ASAN / LeakSanitizer. I have a leak-causing mistake, what, twice a year maybe ? Definitely not something worth loosing sleep over.

The much bigger problems imho are not really tractable. e.g. consider something that does:

    struct server { 
      std::vector<requests> m_requests_to_process;
      boo m_running{true};

      void on_web_request(request req) {
        m_requests_to_process.push_back(req); 
      }

      void process_pending_requests() {
        while(m_running) { 
          for(auto req : m_requests_to_process) {
            do_stuff(req);
          }
          // oops, forgot to clear
        }
      }
    };

there's no "leak" in the OS sense as everything will be reclaimed upon server::~server... but your memory usage will be strictly increasing. Can Rust detect those cases ? As in my experience this is the most common leak pattern (of course not in a simple example like this, but when your "server" architecture starts being split across multiple threads and source files...
2 comments

Rust doesn't prevent memory leaks.

However, it may help in this specific case.

In your example you are iterating over `m_requests_to_process`. As you are using `auto` instead of `auto &` it automatically clones the elements of a vector. In Rust, it's not possible to clone an element by accident. Objects are moved by default (think `std::move`) and if you want to clone instead you need explicitly use `.clone()`.

If `do_stuff` function takes ownership of a request (as in, if it takes `Request` parameter and not `&mut Request` parameter) than the problem will be pointed by Rust, and so the compiler wouldn't allow you write code like this forcing programmer to write code that removes elements from the list to take ownership of them. For example:

    for req in mem::take(&mut self.requests_to_process) {
        do_stuff(req);
    }
`mem::take` (https://doc.rust-lang.org/std/mem/fn.take.html) replaces an object behind a mutable reference with a default value for a given type (empty vector in case of vectors) and gives you an ownership over vector.
You don't even need `std::mem::take`, you can just `drain` the `Vec`. This also allows the allocation to be reused, unlike your `mem::take` which ends up dropping the allocation.

`Vec::drain`: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.dra...

imagine that my struct request looks like

   struct request { 
     int id;
     double parameter_value;
   };
surely moving does not gain anything - the original vector object still keeps the memory allocated, right ? sure, if you have complex requests with substrings, etc, but in that case I'd have `const auto&`-ed :)

(from my experience going full-throttle on movability when C++11 came out, I'd say that this was a mistake overall, much better to keep things as const& most of the time if you can. I've not yet reached a state where I consider the need for ownership transfer a code smell... but not very far :-))

With the implementation your parent posted, the entire vector would be replaced with a fresh one. So the original vector would be deallocated entirely.

You could also do something with the drain method (which they posted originally and changed to the current implementation, not 100% sure why) and that would keep the memory around, yes, but then you'd be with only the high water mark of the number of requests, because it would be re-used.

Rust does not attempt to stop leaks. It does make it less likely to happen, but completely preventing them is not even on the table.

This particular case might be because you would probably consume the vector in the loop, which would free, but you could absolutely write the exact same code in Rust with the same end result.