3 ms·
I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it st
by uecker 1y ago
I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it step by step. For C++ this is much more painful because all the tools invented to make the code look concise make it extremely hard to follow and change. From the feature set, I would say that Rust is the same (but I have no experience refactoring Rust code).
- AstralStorm 1y agoIn personal experience, the much stronger and composite type model in Rust makes it easier to refactor. Adding features in particular is a breeze and automatically the compiler/language will track for you the places that use only old set of traits. Tooling is still newer though and needs polish. Generic handling is interesting at times and there are related missing features for that in the language, vis a vis specializations in particular. Basic concurrency handling is also quite different in Rust than other languages, but thus usually safer.
- uecker 1y agoI am wondering about this. In C the nice thing is that you usually change things locally. A line of code does not depend on a million other things. I can't quite see how this works in Rust... At least in C++, template, overloading, etc. can make a single line depend on a lot of things.
- Expurple 1y ago> A line of code does not depend on a million other things. But it does! To qoute my top-level comment: > What about about race conditions, null pointers indirectly propagated into functions that don't expect null, aliased pointers indirectly propagated into `restrict` functions, and the other non-local UB causes? In other words: you set some pointer to NULL, this is OK in that part of your program, but then the value travels across layers, you've skipped a NULL check somewhere in one of those layers, NULL crosses that boundary and causes UB in a function that doesn't expect NULL. And then that UB itself also manifests in weird non-local effects! Rust fixes this by making nullability (and many other things, such as thread-safety) an explicit type property that's visible and force-checked on every layer. Although, I agree that things like macros and trait resolution ("overloading") can be sometimes hard to reason about. But this is offset by the fact that they are still deterministic and knowable (albeit complex)
- uecker 1y agoThis is true to some degree, but not really that much in practice. When refactoring you just add assertions for NULL (and the effect of derefencing a NULL in practice is a trap - that it UB in the spec is completely irrelevant. in fact it helps because it allows compilers to turn it a trap without requiring it on weak platforms). Restrict is certainly dangerous, but also rarely used and a clear warning sign, compare it to "unsafe". The practical issues in C are bounds checking and use-after-free. Bounds checking I usually refactor into safe code quickly. Use-after-free are the one area where Rust has a clear advantage.
- Expurple 1y agoI agree that in practice NULL checking is one of the easier problems. I used it because it's the most obvious and easy to understand. I can't claim which kinds of unsoundness in C code are more common and problematic in practice. But that's not even interesting, given that safe Rust (and other safe languages) solve every kind of unsoundness. > in fact it helps because it allows compilers to turn it a trap without requiring it on weak platforms The "shared xor mutable" rule in Rust also helps the compiler a lot. It basically allows it to automatically insert `restrict` everywhere. The resulting IR is easier to auto-vectorize and you don't need to micro-optimize so often (although, sometimes you do when it comes to eliminating bound checks or stack copies) > Restrict is certainly dangerous, but also rarely used and a clear warning sign, compare it to "unsafe". It's NOT a clear warning sign, compared to `unsafe`. To call an unsafe function, the caller needs to explicitly enter an `unsafe` block. But calling a `restrict` function looks just like any normal function call. It's easy to miss an a code review or when upgrading the library that provides the function. That the problem with C and C++, really. The `unsafe` distinction is too useful to omit.
- uecker 1y agoYou are not wrong, but also not really right. In practice, I never had a problem with "restrict". And this is my point about Rust being overengineered. While it solves real problems, you need to exaggerate the practical problems in C to justify its complexity.
- rstuart4133 1y ago> I can't quite see how this works in Rust... You won't understand it unless refactor some Rust programs. Bunny summed it up rather well. He said in most languages, when pull on some thread, you end disappearing into a knot and you're changes are just creating a bigger knot. In Rust, when pull on a thread, the language tells you where it leads. Creating a bigger knot generally leads to compile errors. Actually he didn't say that, but I can't find the quote. I hope it was something like that. Nonetheless he was 100% spot on. That complexity you bemoan about the language is certainly there - but it's different to what you have experienced before. In most languages, complex features tend lead to complex code. That's what made me give up on Python in the end. When you start learning Python, it seems a delightfully simple yet powerful language. But then you discover metaclasses, monkey patching, and decorators, which all seem like powerful and useful tools, and you use them to do cool things. I twisted Python's syntax into grammar productions, so you could write normal looking python code that got turned into an LR(1) parser for example. Then you discover other peoples code that uses those features to produce some other cute syntax, and it has a bug, and when you look closely your brain explodes. As you say C doesn't have that problem, because it's such a simple language. C++ does have that problem, because it's a very complex language. I'm guessing you are making deduction from those two examples that complex languages lead to hard to understand code. But Rust is the counter example. Rust's complexity is forces you to write simple code. Turns out it's the complexity of the code that matters, not the complexity of the language.
- uecker 1y agoI only have limited experience with Rust (only playing around a bit). But it seems the language forces you to structure the code in a specific way (and you seem to agree). This is good, because it prevents you from making a mess (at some level at least). But what others report (and it matches my limited experience) is that it makes the structure of the code very rigid. So I do not quite see how this does not limit refactoring? What some of you describe in this thread is type-directed refactoring (i.e. you change a type and the compiler tells what you need to change), but is this not limited to relatively basic changes? In C, you can make partial changes and accept a temporary inconsistency. This gives you a lot of flexibility that I find helpful.
- pjmlp 1y agoPreprocessor enters the scene. Then we have the functions that might be re-entrant or not, in the presence of signals, threads,...
- bryanlarsen 1y ago> but I have no experience refactoring Rust code But you're willing to write many comments complaining that Rust is hard to refactor. Rust is the easiest language to refactor with I've ever worked in, and I've used a couple dozen or so. When you want to change something, you change it, and then fix compiler errors until it stops complaining. Then you run it, and it works the first time you run it. It's an incredible experience to worry so little about unknown side-effects.
- Dylan16807 1y ago> But you're willing to write many comments complaining that Rust is hard to refactor. Their refactoring comments look focused on C versus C++ to me, with a bit of guessing Rust is like C++ in a way that is clearly labeled as speculation. So I don't see the problem with anything they said about refactoring.
- bsder 1y ago> Rust is the easiest language to refactor with I've ever worked in Oh? So how do you refactor a closure into a named function in Rust? I have found this to be of the most common failure modes that makes people want to punch the monitor. (Context: in almost all programming languages, a closure is practically equivalent to an unnamed function--refactoring a closure to a named function tends to be pretty straightforward. This isn't true for Rust--closures pick up variable lifetime information that can be excruciatingly difficult to unwind to a named function.)
- bryanlarsen 1y agoSet the type of all the parameters to your new function to bool, then compile. The compiler error will tell you, "Error: you passed a foo<'bar, baz> instead of a bool". Then you change the type in the function's parameter to "foo<'bar, baz>".
- afdbcreid 1y agoYou invoke the assist of rust-analyzer that does that automatically.
- 1y ago
- anon-3988 1y ago> I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it step by step. Really? How confident are you to change a data structure that uses an array with linear search lookup to a dictionary? Or a pointer that now is nullable (or is now never null)? Unless you have rigorous test or the code is something trivial, this would be a project of its own. I am pretty sure I can swap out the implementation of the dictionary in the rust compiler and by the time the compilation issues are worked out, the code would be correct by the end of it (even before running the tests)
- sigi64 1y agoI can compare programming and refactoring large code bases in C and C++, Rust, and Python, from system and parsing and protocol libraries to asynchronous multi-threaded servers in the mentioned languages. Refactoring Rust projects is clearly the easiest because the compiler and type system ensure the program is correct at least in terms of memory access and shared resource access. It doesn't protect me from memory leaks and logical errors. But since Rust has a built-in testing framework, it's quite easy to prepare tests for logical errors before refactoring. C/C++ refactoring is a nightmare - especially in older projects without modern smart pointers. Every change in ownership or object lifetime is a potential disaster. In multi-threaded applications it's even worse - race conditions and use-after-free bugs only manifest at runtime, often only in production. You have to rely on external tools like Valgrind or AddressSanitizer. Python has the opposite problem - too much flexibility. You can refactor an entire class, run tests, everything passes, but then in production you discover that some code was dynamically accessing an attribute you renamed. Type hints help, but they're not enforced at runtime. Rust forces you to solve all these problems at compile time. When you change a lifetime or ownership, the compiler tells you exactly where you need to fix it. This is especially noticeable in async code - in C++ you can easily create a dangling reference to a stack variable that an async function uses. In Rust, it simply won't compile. The only thing where Rust lags a bit is compilation speed during large refactors. But that's a small price to pay for the certainty that your code is memory-safe. Another area where Rust absolutely excels is when using AI agents like Claude Code. It seems to me that LLMs can work excellently with Rust programs, and thanks to the support of the type system and compiler, you can get to functional code quickly. For example, Claude Code can analyze Rust programs very well and generate documentation and tests. I think Rust with an AI agent has the following advantages: Explicit contract - the type system enforces clear function interfaces. The AI agent knows exactly what a function expects and what it returns. Compiler as collaborator - when AI generates code with an error, it gets a specific error message with the exact location and often a suggested solution. This creates an efficient feedback loop. Ownership is explicit - AI doesn't have to guess who owns data and how long it lives. In C++ you often need to know project conventions ("here we return a raw pointer, but the caller must not deallocate it"). Fewer implicit assumptions - in Python, AI can generate code that works for specific input but fails on another type. Rust catches these cases at compile time.