Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

To be honest I just clone and unwrap everywhere when writing the first iteration. If I care more, then I'll refactor to remove unnecessary clones. I really haven't felt the pain of the borrow checker like some people say.


sort by: page size:

Also I think that with enough .clone(), raw pointers and `unsafe` you can avoid almost all borrow checkers errors.

Satisfying the borrow-checker usually ends up implying that either:

a) you used .clone() everywhere and therefore are probably modifying something and failing to propagate the modifications to where they need to go; or

b) your design is inherently better, saner, easier-to-optimize, etc., than one which doesn't have to satisfy the borrow checker.


Yes, exactly. One of my first big fights with the borrow checker was over not realizing I was invalidating an iterator by mutating it by looping over it. Then it clicked for me.

Don't you find thinking about the borrow checker unnecessary overhead while prototyping?

My experience is that the borrow checker is not that hard to deal with. There are two ways:

- you are a c/c++/low level guy who knows how allocation works very well and figure it out

- you are a java/c#/python guy who wants to get going and use clone() a lot

Either way, the performance that you get is worth the troubles.


> I'd clone to convince the borrow checker to let me move on, would construct new containers rather than pass new ones in to be refilled and the like. Also, strings. Lots and lots of strings everywhere.

This is where I'm at right now. When the borrow checker yells at me, I find I can generally fix it with a .clone() or a .to_string(). I'm new enough with the borrow checker that I'm not quite sure when such cloning is necessary and when I'm unnecessarily allocating more memory -- or more importantly, what I should be doing instead when it's not necessary -- but I hope that as I write more code, I'll get better at it :)

A bad habit that I've fallen into is to needlessly allocate in order to avoid having to think about lifetimes. As in, better change that struct to make that str a String -- and then not have to worry about lifetimes at all. That's just laziness, though...


Probably not; detecting unnecessary clones is expensive for non-trivial cases. If a human can't prove that the clone isn't necessary (by writing the code in such a manner that the borrow checker accepts), I doubt the compiler could.

> Also btw "borrow checker fighting" is not much of an issue for experienced Rust developers. You quickly get used to how to write code to avoid borrow checker issues. And in general, writing code that way tends to be a good thing anyway.

By copying more?


Hang in there. Lifecycles manage themselves, mostly. You can write large, complete applications without handling a single explicit lifetime. The borrow checker becomes second nature once you get the hang of it. Don't feel bad for using Rc/Refcell/Clone everywhere. Your code will still be faster than it would have been in most other languages.

Stop caring about writing .clone() until you feel comfortable enough to care.

The biggest thing I see people go crazy with in Rust is trying to avoid cloning things. I've watched it block people from progressing in the language. Yes, you ultimately don't want to clone everything everywhere, but getting something built in Rust and then iterating backwards is often a better way to learn the language than trying to be "correct" from the start.

Cloning often removes any need to deal with lifetime signatures or what-have-you. If you're bothered by a full clone, grab Rc/Arc and use that trade-off in the beginning - it's not going to kill you. These changes make the syntax of the language fairly comparable to other languages.

tl;dr: .clone() things, measure if it's a problem, and then work backwards to the state you feel that the language/borrow-checker/whatever is pushing you towards.


For me, the borrow checked removes a lot of mental overhead. It finds all sorts of subtle concurrency bugs in my code.

When you've internalized its rules, the borrow checker isn't too complicated to keep happy (you may need to use stuff like RefCell and Rc though). It takes some time though, and this tutorial helped me a lot in the beginning: https://rust-unofficial.github.io/too-many-lists/

But you don’t fight meaninglessly with the borrows checker. For me, this was only the first few weeks. Once you know the (simple) borrowing rules, it is rarely a problem anymore. It only rears its head every now and then when you know something is valid, but the borrows checker is not able to infer it.

Another dimension to it is that the borrows checker rules also apply in most other languages if you want to write valid code. They are just not enforced and you have to rely on your own discipline instead (which humans are bad at).


I think everyone agrees that "fighting" the borrow checker is annoying, however at the risk of sounding like a Rust apologist I find that a couple interesting things have happened over ~2 years with the language:

1. The borrow checker usually kicks in when I want to take shortcuts that would bite me in the future. 90% of issues I hit with it can be solved with copy/clone.

2. When I take the time to satisfy the borrow checker I find my architecture is better. Usually I'm tripping the borrow checker because I don't have a clear single-owner or separation of concerns.

Also, if you're looking for an "easy" to program languages there's tons of them out there and it definitely doesn't make sense in my mind to use Rust where C#/Erlang/Java/etc fit the domain space better.


I'm inclined to agree. It took me a week or two of daily usage to finally "get used" to the borrow checker. It's a bit of a paradigm shift, to be sure, but it's not insurmountable. In fact, I think learning Rust has made even my C code better, because now I'm in the habit of thinking thoroughly about ownership and the like, which is something that I did to an extent before (because you have to to write robust software without a GC), but it was never explicit and I never would've been able to articulate the rules like I can now.

That said, I still occasionally have problems where I feel like I'm doing something "dirty" or "hacky" just to satisfy the borrow checker. It's easy to program yourself into a corner and then find yourself calling `clone()` (the situation, I've been told, has gotten much better in recent releases with improvements to the borrow checker, but alas I haven't had a chance to play much with Rust in nearly a year).

Another thing that I still find difficult is dealing with multiple lifetimes in structs, to the point that I usually just say "to hell with it" and wrap everything in an `Rc<T>`. And sometimes there's simply no safe way (afaict) to do some mutation that I want to do without risking a panic at runtime (typically involving a mutable borrow and a recursive call), which leads to a deep re-thinking of some algorithm I'm trying to implement. That's not Rust's fault, though—it's a real, theoretical problem that arises in the face of mutation. In time, I'm sure there will be well-understood patterns for handling such cases.


I kinda agree, it's weird to me how people make a big deal about the borrow checker as if it's an evil spirit gatekeeping your code.

And if you wanna go fast instead of writing maintainable code you surely can with stuff like unwrap(), clone(), Copy.

In a way I think most people expect to pick up Rust more easily because they have expertise in other languages and are surprised when Rust feels like learning to code again, basically turning some ingrained assumptions upside down.

It really is not the kind of language where you can glaze over the introduction chapter and churn a TODO app in the same afternoon in my opinion.


It was also my feeling in the beginning, but the borrow-checker struggle goes away with experience.

Once I "got" how to structure my programs in a Rust-friendly way, it's got nice and easy to use.


Clicking with the borrow checker is a big hurdle for many, but once it clicks and you adjust your design patterns to be compatible with the language it really is a secondary concern.

I very rarely run into borrow check errors, and if I do they are usually easily solved. From my impression that's the same for most semi-experienced Rust devs.


I would be curious to know more about this case too, fwiw!

Personally, I've only struggled a bit with the borrow checker in a couples of cases: trying to have self/circular references when trying to model OOP, and when fiddling/interacting with some low level async code. Both were unnecessary in my case, and each situation was caused by a lack of understanding.

next

Legal | privacy