Hacker News new | past | comments | ask | show | jobs | submit login

A borrow checker that isn't "viral all the way down" allows use-after-free bugs. Pointers don't stop being dangling just because they're stashed in a deeply nested data structure or passed down in a way that [[lifetimebound]] misses. If a pointer has a lifetime limited to a fixed scope, that limit has to follow it everywhere.

The borrow checker is fine. I usually see novice Rust users create a "viral" mess for themselves by confusing Rust references with general-purpose pointers or reference types in GC languages.

The worst case of that mistake is putting temporary references in structs, like `struct Person<'a>`. This feature is incredibly misunderstood. I've heard people insist it is necessary for performance, even when their code actually returned an address of a local variable (which is a bug in C and C++ too).

People want to avoid copying, so they try to store data "by reference", but Rust's references don't do that! They exist to forbid storing data. Rust has other reference types (smart pointers) like Box and Arc that exist to store by reference, and can be moved to avoid copying.




> Pointers don't stop being dangling just because they're stashed in a deeply nested data structure or passed down in a way that [[lifetimebound]] misses

This is the typical conversation where it is shown what Rust can do by shoehorning: if you want to borrow-borrow-borrow from this data structure and reference-reference-reference from this function, then you need me.

Yes, yes, I know. You can also litter programs with globals if you want. Just avoid those bad practices. FWIW, references break local reasoning in lots of scenarios. But if you really, really need that borrowing, limit it to the maximum and make good use of smart pointers when needed. And you will not have this problem.

It looks to me like Rust sometimes it is a language looking for problems to give you the solution. There are patterns that are just bad or not adviced in most of your code and hence, not a problem in practice. If you code by referencing everything, then Rust borrow-checker might be great. But your program will be a salad of references all around, which is bad in itself. And do not get me started in the refactorings you will need every time you change your mind about a reference deep somewhere. Bc Rust is great, yes, you can do that cool thing. But at what cost? Is it even worth?

I also see all the time people showing off the Send+Sync traits. Yes, very nice, very nice. Magic abilities. And what? I do my concurrent code by sharing as little as possible all the time. So the patterns of code where things can be messed up are quite localized.

Because of this, the borrow checker is basically something that gets a lot in the way but does not add a lot of value. It might have its value in hyper-restricted scenarios where you really need it, and I cannot think of a single scenario where that would be really mandatory and really useful for safety except probably async programming (for which you can do structured concurrency and async scopes still in C++ and I did it successfully myself).

So no, I would say the borrow checker is a solution looking for problems because it promotes programming styles that are not clean from the get go. And only in this style it is where the borrow checker shines actually.

Usually the places where the borrow checker is useful has alternative coding patterns or lifetime techniques and for the few ones where you really want something like that, probably the code spots are small and reviewable anyway.

Also, remember that Rust gives you safety from interfaces when you use libraries, except when not, bc it basically hides unsafe underneath and that makes it as dangerous as any C or C++ code (in theory). However, it should be easier to spot the problems which leads more safety in practice. But still, this is not guaranteed safety.

The borrow checker is a big toll in my opinion and it promotes ways of coding that are very unergonomic by default. I'd rather take something like Swift or even Hylo any day, if it ever reaches maturity.


In general I view the borrow checker as a good friend looking over my shoulder so I don't shoot myself in the foot in production. 99 times out of 100 when the borrow checker complains it's because I did something stupid/wrong. 0.99 times out of 100 I think the borrow checker is wrong when I am in fact wrong. 0.01 times out of 100 the borrow checker fumbles on a design pattern it maybe shouldn't so I change my design. Usually my life is way better for changing the design after anyways.

The thing is, you don't need to have refs of refs of refs of refs of refs. You can clone once in a while or even use a smart pointer. You'll find in 99.99% of cases the performance is still great compared to a GC language. That's a common issue for certain types of people learning how to write Rust. I can't think of any application that needs everything to be a reference all the time in Rust.

As far as "mandatory" goes for choosing a language. We can all use ASM, or C, write everything from scratch. It's a choice. Nothing is mandatory. No one is saying you HAVE to use Rust. Lots of people are saying "when I use it my life is way better", that's different. There was a recent post here where people say they don't use IDE's with LSP or autocomplete. A lot of people are going to grimace at that, but no one is saying they can't do that.


> I also see all the time people showing off the Send+Sync traits. Yes, very nice, very nice. Magic abilities. And what? I do my concurrent code by sharing as little as possible all the time. So the patterns of code where things can be messed up are quite localized.

They check whether your code really shares as little as you think, and prevent nasty to debug surprises.

The markers work across any distance, including 3rd party dependencies and dynamic callbacks, so you can use multi-threading in more situations.

You're not limited to basic data-parallel loops. For example, it's immensely useful in web servers that run multi-threaded request handlers that may be calling arbitrary complex code.

> places where the borrow checker is useful has alternative coding patterns

There's a popular sentiment that smart pointers make borrow checker unnecessary, but that's false. They're definitely helpful and often necessary, but they're not an alternative to borrow checking.

Rust had smart pointers first, and then added borrowing for all the remaining cases that smart pointers can't handle or would be unreasonable to use.

Borrowing checks stack pointers. Checks interior pointers to data nested inside of types managed by smart pointers (so you don't have to wrap every byte you access in a smart pointer). It allows functions safely access data inside unique_ptr without moving it away or switching to shared_ptr. Prevents using data protected by a lock after the lock has been unlocked. Prevents referencing implicitly destroyed temporary objects. Makes types like string_view and span not a footgun.

> the borrow checker is basically something that gets a lot in the way

This is not the case for experienced Rust users.

Borrow checker is a massive obstacle to learning and becoming fluent in Rust. However, once you "get" it, it mostly gets out of the way.

Once you internalise when you can and can't use borrowing, you know how to write code that won't get you "stuck" on it, and avoid borrow checking compilation errors before they happen. And when something doesn't compile, you can understand why and how to fix it. It's a skill. It's not easy to learn, but IMHO worth learning more than C++'s own rules, Core Guidelines, UB, etc. that aren't easy either, and the compiler can't confirm whether you got them correct.


> Borrowing checks stack pointers. Checks interior pointers to data nested inside of types managed by smart pointers (so you don't have to wrap every byte you access in a smart pointer). It allows functions safely access data inside unique_ptr without moving it away or switching to shared_ptr. Prevents using data protected by a lock after the lock has been unlocked. Prevents referencing implicitly destroyed temporary objects. Makes types like string_view and span not a footgun.

I understand part of the value the borrow checker brings. Actually my complaint it is more about having a full borrow checker and viralize everything than about having the analysis itself. For example Swift and Hylo do some borrow-checking analysis but they do no extend that to data structures and use reference counting (with elision I think) and value semantics.

The problem with the borrow checker is not the analysis. It is the virality. Without the virality you cannot express everything. But with the amount of borrow checking that can be done through other conventions (as in Hylo/Swift) and leaving out a part of the story I think things are much more reasonable IMHO.

There are so many ways to workaround/just review code in(assuming the cases left are a bunch of those) the remaining spots that presenting a fully viral borrow checker to be able to represent so many situations (and on top of that promoting references everywhere, which breaks local reasoning) that I question the value of a full borrow checker with full virality. It also sets the bar higher for any refactoring in many situations.

> Borrow checker is a massive obstacle to learning and becoming fluent in Rust. However, once you "get" it, it mostly gets out of the way.

This is just not true for many valid patterns of code. For example, data-oriented programming seems to be a nightmare with a borrow checker. Linked structures are also something that is difficult. So it is not only "getting it", it is also that for certain patterns it is the borrow checker who "gets you", in fact, "kidnaps you away" from your valid coding patterns.

> but IMHO worth learning more than C++'s own rules, Core Guidelines, UB, etc

I admit to be more comfortable with C++ so it is my comfort zone. But there are middle solutions like Swift or (very experimental) Hylo that are worth a try IMHO. A full, embedded borrow checker with lifetime annotations is a big ergonomy problem that brings value if you abuse references, but when you do not, the value of the borrow checker is lower. Same for escaping references several levels up... why do it? I think it is just better to try to avoid certain coding patterns. Not because of Rust itself. Just as general coding style in any language...

> that aren't easy either, and the compiler can't confirm whether you got them correct.

Not all as of today, but a subset yes, there are linters. Also, there is an effort to incrementally increase the value of many analysis. It will never be as perfect as Rust's, I am sure of that. But I am not particularly interested either. What I would be more interested in is if with what can be fixed and improved the delivered software has the same defect rates as Rust lifetime-wise. This is counter-intuitive bc it looks like the better the analysis, the better the outcome, but here two factors also play the game IMHO:

  1. not all defects are evenly distributed. This means that if the things that can be lifetime-checked are a big amount of typical lifetime checks in C++, even if not all kinds such as Rust's can be done, it can get statistically very close.
  2. once the spots for unsafe code are more localized, I expect the defects rate to decrease more than linearly, since now the attention is focused on fewer code spots.
Let us see what comes from this. I am optimistic that the results will be better than many people predict in ways that look to me too academic but without taking into account other factors such as defect density in clusters and reduction of surface to inspect by humans bc it cannot be verified to be safe.




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

Search: