The opposite, it makes refactoring really easy, because it's so hard to screw up mechanical changes.
The memory management aspect of refactoring is slightly more tedius in rust (compared to go), because you have to update lifetime annotations sometimes. This is a mechanical process and not hard. The trade off is that the existence of lifetimes and ownerships means that many of the rest of refactoring tasks (getting locks right, not introducing data races, making sure you're not doing something dumb like accessing a freed fd, etc) are much simpler.
Rust is also better than go at refactoring because go's ducktyping (and reflection) means that there is frequently action at a distance that is hard to track down. Changing the name of a field of a struct can change behavior in another file that never mentions the field by name.
Go is in the middle of adding generics to the language, I don't think Rust has made a change of that magnitude since it hit 1.0...
It is much more complicated. You sometimes have to implement clone, pin/project, box etc to satisfy new trait bounds for example. Also, build times can make refactoring and iterating really painful.
I like and use both languages; I think they are complimentary, and I think things need to be weighted more honestly than what I see in this discussion.
My honest opinion is that it is 2021 and it's about time we let computers manage memory already.
Rust can have their ownership for race conditions if they want, but I am so tired of being gaslighted about how important it is that I be hounded to track every solitary byte of memory when I am busy thinking about more important things.
On the contrary, ownership tracking is important for general code correctness, not just memory management. It's practically impossible to audit and assess correctness of a program with pervasive confusion in the overall patterns of what code owns what data. Memory safety is also important of course, but it's mere table stakes.
Is the antidote to those inefficiencies an ownership model that forces you to reason about mutability? I don't know. I kind of think no, primarily that it is a performance consideration that infects the API and spreads everywhere; it's a distraction. Is it instead to have all immutable data structures and an insanely smart compiler that replaces copying with in-place mutation, so that pure functional languages compete with highly tuned, pervasively mutable code? I kind of also think, no. And primarily that's because performance cliffs get worse and harder to predict, the smarter your compiler is.
The mutability/ownership question is confounded with allocation and deallocation. The latter really should never be on any programmer's mind, IMHO. In Rust, it seems there isn't much support for decoupling the two, e.g. by having an automatic garbage collector. That's also an unfortunate reality forced on language implementers by the fact that LLVM has steadfastly refused to support stackmaps as a first class concept for more than 15 years. Many, many projects have died because of LLVM's stackmap support being lacking or broken. That's unfortunate because precise stackmaps are a key enabler for many GC techniques, and without them, you end up with some form of conservatism that make certain optimizations impossible, and forces a particular design for nurseries.
> I kind of think no, primarily that it is a performance consideration that infects the API and spreads everywhere; it's a distraction.
is something I fundamentally disagree with; unfortunately, I don't really have the time at this moment to even get it into the level that you have here, but it's a good conversation to have, and a needed level of details to even have a good conversation.
This is completely irrelevant because stop the world pauses are global and affect your entire application no matter how careful you write your code. Every library you use has to be carefully written, every single line of code you write has to be carefully written regardless of whether it is time critical or not, this is a complete dead weight for the entire application.
With isolated GC heaps your time critical code will be isolated from non critical code. In practice the easiest way to do this is to just launch a separate application. This hurts a lot with the JVM because it wants to own your entire server (mostly RAM) for some inexplicable reason and you might as well write that part in C++ if you do inter process communication anyway.
The real use case for GC is managing data where there's no well-defined "ownership" pattern, such as when dealing with general graphs (e.g. in a symbolic computing or GOFAI application). That's a remarkably niche domain.
Not having to worry about that is very nice. Using less memory, I agree it's not terribly important but it is a nice bonus - which comes in handy when working on embedded, videogames or in constrained conditions (like when you can put a lower limit to your containers and run more containers on the same machine).
Garbage collection lets you avoid memory leaks, double-free, use-after-free, and that's it. In Go, it's up to you to correctly handle every other type of resource correctly, as the compiler won't offer you any help.
An ownership system like Rust's is a general-purpose tool that handles all kinds of resource management. File descriptors, database transactions, session types, locks, etc.
People praise Go's channels, but Go channels are way more complicated to use correctly, and part of that is due to ambient ownership (the other part is due to bad error handling). You need to manually keep separate track of how many outstanding writers there are are, and then explicitly close the channel. In Rust, the channel is closed when all senders or all receivers are dropped. I don't have to do anything, and it's always as correct as what I'd have to implement and maintain myself in Go.
Another great example is how Rust's mutex can own the guarded value. In most languages, a mutex is a separate object, and it's up to you to be careful to make sure you never access it without acquiring the mutex first, to always release after the last use of the guarded value, and to never keep a reference to the guarded value after releasing the mutex.
With Rust, the ownership system is used to handle and verify all of that, so I do less work, have less to think about it, and can't make those mistakes. The Mutex owns the value. Mutex::lock() and ::try_lock() return a guard that you dereference to access the protected value. The mutex is released when the guard is dropped. The guard borrows from the mutex, so any attempt to keep it around longer than you have access to the mutex is flagged by the compiler.
My honest opinion is that it is 2021 and it's about time we let computers manage all of our resources, instead of only memory.
Addressing the last part of your comment, that doesn't sound like my experiences writing Rust at all. Regarding memory management and ownership, the majority of the code I write in Rust, I just type the obvious thing and it just works. The majority of the remainder, the compiler points out that I've done something dumb, and it's a trivial fix. The rest of the time, I'm trying to do something genuinely complicated, and the compiler helps me figure out and verify the work in pieces, rather than trying to cram the whole thing in my head at once, and then convince myself that it's correct. When I'm finished, I feel confident that the compiler will catch errors introduced by future maintenance and refactoring, rather than having to cram it all back into my brain at once every time I touch the code.
To me, in both Rust and Go, the trivial cases are trivial. For more-interesting cases, Rust lets me just do the thing and focus on thinking about my problem at a higher level while trusting that the compiler will handle the details. For more-interesting cases with Go, I have to keep track of a bunch of little details and make sure I explicitly clean up every little resource I ever interact with.
I really like the discussion on resource types vs data types in this essay on higher-level rust-inspired languages: https://without.boats/blog/revisiting-a-smaller-rust/
What Go defer lacks, is the ability like e.g. .NET with IDispose, where I can make of Roslyn Analyzers to break builds when I forget to call using on such resources.
Rust is a good option for kernel code and drivers, beyond that there is no point in throwing away the productivity of having a GC around.
The fact that you haven't mentioned these low hanging fruit makes me feel like you haven't thought your argument through. You're advocating for a half baked solution even though there is a perfect solution that almost nobody tapped into that would make the GC superior almost everywhere. That's why you are wrong and why nobody should listen to you and people should listen to the creators of Erlang and Ponylang instead.
In the field where I am working (realtime audio), that is just not possible. I don't want to have a garbage collector pause in the audio thread. In fact, I can't do any dynamic memory allocations at all (without a special realtime memory allocator). Other fields, like medical devices, flight control, embedded systems, etc. have even tighter constraints.
Manual memory management is not only about performance, but also about predictability. This kind of low level control is one of the main reasons why people use C/C++ in the first place.
I don’t code in go so this looked surprising to me. TypeScript has a similar feature regarding types, and it will tell you if you change a field in a record, and that record is actually used in a place where another record with the same fields is asked for.
Kind of surprised go has this problem.
Apart from serialization I've also seen this with ORMs, and some of internal code for metrics. I imagine there are other cases too... but while I currently work in go I'm not really that much of an expert in the language.
But how is it any better than Java or C#?
And Rust is a low level language, so it has to get away with rare cases where a GC is not applicable. And being low level of course imposes the memory layout of the program on APIs as well, but that is a known trade off when one goes low level.
Rust is unbeatable for services you want to deploy, stabilize, and forget about forever.
Go is pretty darn good for building out complicated/messy services in an iterative process.
Erlang.
C or C++ could have a GC as well (in fact, they do - you can use Boehm for any C or C++ program), but there are approximately 0 programs that do.
Baking ownership into the type system makes API design and refactoring hard, while having GC makes memory management entirely orthogonal and easy to change. Go is already over 10 years old and designed by people who have worked on software for 30, sometimes 40, years. Go changes slowly and Rust is still evolving.