Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Let me try to identify which ones Rust catches:

"#1: Using a shared pointer where an unique pointer suffices."

The Rust analog would be using Rc or Arc when Box is enough. Rust has the same problem.

"#2: Not making resources/objects shared by shared_ptr thread safe"

Rust does NOT have this problem! You have to be explicit about which objects are thread safe (via Sync), and Rust will not allow you to transfer objects across threads without it.

"#3 : Using auto_ptr"

Naturally, Rust does not have this issue, which is strictly legacy C++.

"#4 Not using make_shared to initialize a shared_ptr !"

"#5 Not assigning an object(raw pointer) to a shared_ptr as soon as it is created"

"#6 Deleting the raw pointer used by the shared_ptr"

These are the same issue: in C++ you can construct a shared_ptr from an object you allocated with new; the reference count must then be allocated separately. Rust does not allow this, and avoids this issue, at the cost of some flexibility (like custom deallocators).

"#7 Not using a custom deleter when using an array of pointers with a shared_ptr"

AFAIK Rust doesn't have this issue, because it doesn't have C++'s array-decays-to-pointer semantics. In Rust the size of the array is part of the type; to be fair, this loses a bit of flexibility, but C++'s semantics here are insane, so point goes to Rust.

"#8 Not avoiding cyclic references when using shared pointers !"

Rust has this problem!

"#9 Not deleting a raw pointer returned by unique_ptr.release() "

Rust has an analog in Box::into_raw(), which is the same issue.

"#10: Not using a expiry check when calling weak_ptr.lock()"

Close one: Rust has the same issue, in that you can call rc::Weak::upgrade().unwrap(). unwrap() is often used with some impunity, especially with locks. However you do have to be explicit about your use of it, while in C++ it's easier to forget. Let's give this one to Rust.

Let me give one more point to C++:

"#11: Accessing an object through a Rc/Arc/RefCell while a caller holds a mutable reference."

This is strictly a Rust problem: because there cannot be more than one extant mutable reference to an object, you have to be careful to not call out to something while holding a mutable reference. This is a doozy.

Total score: There are 7 unique to C++, 1 unique to Rust, and 3 that are shared.

Note that none of this has to do with the borrow checker! Indeed, the whole point of reference types like Arc/Rc/RefCell are when your lifetimes are dynamic, and you need to avoid the borrow checker, which assumes a statically knowable lifetime.



Your points are technically correct, but I thought I'd add that Rust offers mitigations for these issues in practice:

1. Using shared ptr where unique ptr would do: This happens a lot less in Rust because (a) in C++, shared ptr predated unique ptr, so you see a lot of legacy code needlessly using shared ownership; (b) Rc/Arc prevents mutation without jumping through hoops (RefCell/Mutex), discouraging its use for ergonomic reasons. Additionally, even when it does happen the reference counts are non-atomic on Rc and have to be twiddled manually, so the overhead is much less than with copy constructors which can be invoked silently and often.

2. Reference cycles: This is a real problem. It's somewhat mitigated by the fact that you need a RefCell or Mutex to construct the cycle. If those are absent, no cycle.

3. Box::into_raw: Well, sure, but this method is really rarely used, due to the lessened need for legacy interop. It probably wouldn't make a list of the top 100 mistakes made with smart pointers in Rust, much less the top 10.

4. Double dynamic borrows: This does happen, but you need RefCell or Mutex to do it. It's not really a smart pointer issue at all. In any case, it's there to enforce memory safety, which C++ doesn't even try to; it's not like C++ does this better than Rust.


The borrow checker is what makes unique pointers in Rust unique. In C++, you can pass ownership but still reference the unique pointer you moved from. It should crash at run time, deferencing null, but the compiler will happily compile it.

Can you actually cause #11 in Rust? Example?

Circular references remain a problem, but they can only cause memory leaks, not security errors or crashes. It would be nice to have a Rust static analysis tool for circular references. Often, you can prove their absence for a type.


Perhaps you consider this splitting hairs, but I would say this isn't the borrow checker at work, because nothing is being borrowed! Instead it's Rust's move semantics, which is separate from the borrow checker. We can envision C++ with no borrow checker, but Rust-style move-semantics-by-default, and it would be a better language for it.

An example of #11:

    use std::cell::RefCell;
    fn main() {
        let c = RefCell::new(5);
        *c.borrow_mut() = *c.borrow() + 1;
    }
this is equivalent to * c = *c + 1, which is conceptually safe, but crashes at runtime in Rust.

A real world example of this: say you are keeping a count of some event in a global or thread local storage. You pull out a mutable reference, so you can update the count more efficiently. You then call a function which accesses that global. Crash!

Rc/Arc have the same issue. This defeats the point of refcounting: it means you must have global knowledge of the refcount in order to reliably call mutating functions on the contents. But if you have that global knowledge, you wouldn't need refcounting in the first place!


  > but crashes at runtime in Rust
Because "crash" is ambiguous, I'll note that this code example panics at runtime (as opposed to segfaulting).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: