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

Usually programmers expect to destroy objects in some specific location. For example, if a widget is removed from a window and then goes out of scope, then most programmers would expect it to be destroyed shortly thereafter. If there are actually references to the object remaining, contrary to programmers' intent, then one of four things can happen:

1. Use-after-free (C, C++). This is a logic bug and a security problem.

2. "Logically" dangling pointers (safe languages with non-generational indices). This is a logic bug, but not a security problem.

3. A memory leak (safe languages with garbage collectors). This is a bug that can be difficult to track down.

4. Panic/exception (safe languages with generational indices or weak pointers). This is a runtime failure.

I'd argue that, of these four options, (4) is generally the best. The ideal would of course be a static guarantee instead of any of these. However, static guarantees always come with restrictions of some kind. For the truly unrestricted case, in which your data references are completely irregular, I'm not sure you can really do better.



The point still stands. If you hold an index into an array of widgets, and the widget you reference is removed or replaced in the array, you could get behaviours 1, 2 or 4 depending on your implementation. Rust's index-into-array pattern removes the possibility of memory leaks, but it also removes most of the benefit of having a borrow checker in the first place.

Its basically a less performant, less ergonomic version of using raw C pointers. Less ergonomic because you need to write your own allocator for your array. And its less performant because fetching the associated struct from memory requires 2 fetches rather than 1. If you're convinced dynamic memory management is the only solution to your data structure, rust's unsafe{} seems a better choice. Its in the language for a reason.


You can't get security vulnerabilities due to use-after-free if you use indices. Don't use unsafe for this.


Sure you can.

If my index 2 now contains the contents of lets say index 5, due to array rearrangement after deletion of the element at 2, whatever happens with data[idx].is_data_valid() is not what the developer would be expecting.


Easily solved with generational indexes as described in https://www.youtube.com/watch?v=aKLntZcp27M


I have seen that talk.

To me it feels a bit like a workaround for something that cannot be validated by the borrow checker, because it is something that developers have to go the extra mile to implement, or get a third party library, which someone has to validate that actually works as expected.

In a way, it isn't much different than expecting C and C++ developers to use static analysis for lifetime validations.

Meaning, using a tool outside of the core language for added safety.


> To me it feels a bit like a workaround for something that cannot be validated by the borrow checker

Is it so hard to believe that references are not the correct abstraction for 100% of use cases? Reaching for something other than references when references are the wrong tool for the job is not working around the borrow checker; it's choosing the right abstraction for the right task. Being hung up on the borrow checker is missing the forest for the trees.

> get a third party library, which someone has to validate that actually works as expected.

How is this different from using any third-party library, ever?

> Meaning, using a tool outside of the core language for added safety.

Rust explicitly supports users defining their own smart pointer types to provide pointer-like abstractions with custom semantics; using tools outside of the core language for added safety (for whatever definition of "safety" one wants) is completely expected and encouraged.


The point being, that for developers focused on managed languages, C++ takes the role of the unsafe layer.

Meaning Java/C++, .NET + (C++/CLI | C++/WinRT), Node.js + C++, Swift / Objective-C++.

So with C++ improving its safety history, usually with ideas taken from Rust, Rust ergonomics and tooling need to have a better story than C++'s to replace it on the above stacks.


Interesting, because in my experience people use Java/C, Node/C, Swift/Objective-C, Python/C, Ruby/C... rarely do I encounter anyone using C++ as an extension language (I encounter Rust being used more often than C++, in fact, but perhaps that's an artifact of the circles I'm in).

As for C++ adding more static analysis, its lifetime analysis is a nice-to-have, but doesn't compare to Rust's borrow checker. You simply can't tack on a sound borrow checker to C++, because the language wasn't designed to accommodate one, and trying to impose the concomitant rules regarding mutability, aliasing, and single-ownership would break every C++ program ever written. For anyone who prioritizes sound static analysis WRT lifetime verification, C++ isn't a competitor to Rust. And there are plenty of people for whom that isn't the case, and they will continue to use C++, and that's not a problem. Rust exists to provide an alternative systems language for people who favor memory safety, and it's pretty good at that. :)


I guess you spend more time in UNIX platforms?

As for being an alternative systems language for people who favor memory safety, I fully agree, my point is that it still needs to improve its productivity and eco-system.

At CppCon 2018 Embedded Development panel, one theme was that only now companies are slowly willing to migrate from C to C++11(!), with a language that allows for a progressive rewrite from C while keeping the existing toolchains.

Another productivity example, with .NET I can get the safety I advocate, while C++/CLI/CX/WinRT allow for a seamless interoperability story with native code.

So even if the lifetime analysis is a subset of what Rust is capable of, mixed debugging and seamless CLR/COM/UWP integration are more attractive than rewriting that code in Rust, without having VS integration and WIP integration with Windows APIs.

I think Rust on its current state, is more indicated for GC free scenarios with either CLI or headless execution.


> I think Rust on its current state, is more indicated for GC free scenarios with either CLI or headless execution.

That's funny, because the largest deployment of Rust is in Firefox, which has a UI.


So can you point us to an example in Firefox's source how to create an UI widget in pure Rust code?

As far as I am aware, Rust is only being used for low level rendering, not widgets.


I can confidently predict that the lifetime profile for C++ will get almost no real-world use.


Requiring a library is only really a big deal in the C/C++ world though. Everywhere else it's trivial, and most projects will depend on some foundational libraries.

That's pretty different to static analysis tools which most likely won't always work.


Nobody is asking for generational indices to be added to the core language. There would be zero benefit. We have a package manager for a reason.

Maybe we could uplift them to the nursery, but again, generational indices are nowhere near the top 10 crates on crates.io.


That is not use-after-free in the way that the security community typically uses the term. This kind of problem is an order of magnitude less likely to lead to RCE.


> 2. "Logically" dangling pointers (safe languages with non-generational indices). This is a logic bug, but not a security problem.

Many logic bugs become security issues. This specific one -- by way of TOCTOU races -- is one of the richest sources of security issues in Unix. The proposed "dormant index" solution is likely to generate a lot of these as well.


> The proposed "dormant index" solution is likely to generate a lot of these as well.

What is a dormant index? I've not seen anything like this proposed, or heard the terminology before.


I borrowed the terminology from ryani’s comment above; it’s the same thing you are discussing iirc, weak reference expressed by an index into a container and some way to identify that container (expressed or implied).

I like the “dormant” name because unlike a weak pointer (say, in Java or Python), you can’t use it directly - you have to borrow the actual object to use it; so it lies “dormant” until you wake it up. It’s not (necessarily) weak because there is no automatic destruction once all other references are gone.


I think the disconnect for me is that circular references just aren't "irregular", they are the natural expression for doubly linked lists, trees with references to parents, etc: these usage patterns are the primary cause of memory issues that I come across in C++.

In other words, static safety in the face of circular references is what would provide me a major clear advantage for better-than-C++, and unfortunately that doesn't seem to be something that is even seen as an opportunity for Rust given all of the rhetoric about that being an abnormal pattern and index-into-array being safe enough under that world.


> In other words, static safety in the face of circular references is what would provide me a major clear advantage for better-than-C++, and unfortunately that doesn't seem to be something that is even seen as an opportunity for Rust.

Having a compiler able to solve the halting problem would also be “a major clear advantage for better-than-C++”, but too bad it's impossible … Arbitrary circular graphs can't be statically managed, that's it. Your need a runtime support, and it will either be a garbage collector, or some kind of constructions with array an indices.


Circular graphs are perfectly possible to detect and manage compile time. It just takes a massively not complex borrow checker to run full dependency analysis to find where and if the cycle is broken or the whole thing released.

Checking for graph cycle is elementary CS - planning the entry/exit nodes is not. It is similar to link time optimization to implement.


> Checking for graph cycle is elementary CS

Yeah, but that's for built graphs, not graphs that will be built at runtime? Seems really, really different.




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

Search: